1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537openWax_utils.ColorsmodulePrinter=Wax_utils.PrintermoduleTrivia=Wax_utils.TriviamoduleStyled=Wax_utils.Styled_printermoduleUint32=Wax_utils.Uint32moduleUint64=Wax_utils.Uint64openAst.Text(*** Printer primitives and sexp model ***)letget_themeuse_color=ifuse_colorthenwat_themeelseno_colortypeformat=Compact|Expansive|Hybrid|Adaptivetypectx={base:Styled.t;format:format;indent_level:int}let_=(Compact,Hybrid,Adaptive)(* The styling and trivia plumbing is shared with the Wax printer in
[Wax_utils.Styled_printer]; only [format]/[indent_level] are specific here. *)letprint_styledctxstyle?(len=None)text=Styled.print_styledctx.basestyle~lentextletprint_triviactxlst=Styled.print_triviactx.baselstletget_triviactxloc=Styled.get_triviactx.baselocletatomic_nodectxlocf=Styled.atomic_nodectx.baselocfletstring_nodectxlocstylelens=atomic_nodectxloc@@fun()->print_styledctxstyle~lenstypesexp=|Atomof{loc:Ast.locationoption;len:intoption;style:style;s:string;}|ListofAst.locationoption*sexplist|Blockof{loc:Ast.locationoption;l:sexplist;transparent:bool;bk:[`Box|`Hov|`Hv];}|Vertical_blockofAst.locationoption*sexplist|Structured_blockofAst.locationoption*structurelistandstructure=Delimiterofsexp|Contentsofsexplistletrecneeds_vertical_layout=function|Vertical_block_|Structured_block_->true|Atom_->false|List(_,l)->List.existsneeds_vertical_layoutl|Block{l;_}->List.existsneeds_vertical_layoutlletrecformat_sexpin_blockdepthfirstctxs=letp=ctx.base.printerinmatchswith|Atom{loc;len;style;s}->string_nodectxlocstylelens|List(loc,l)when(ctx.format=Hybrid&&depth>1)||ctx.format=Compact->lettrivia=get_triviactxlocinprint_triviactxtrivia.before;Printer.boxp(fun()->print_styledctxPunctuation"(";Printer.hvboxp~indent:(ctx.indent_level-1)(fun()->List.iteri(funiv->ifi>0thenPrinter.spacep();format_sexpin_blockdepth(i=0)ctxv)l);print_triviactxtrivia.within;print_styledctxPunctuation")";print_triviactxtrivia.after)|List(loc,l)->lettrivia=get_triviactxlocinprint_triviactxtrivia.before;(if((notin_block)&&ctx.format=Expansive)||List.existsneeds_vertical_layoutlthenPrinter.vboxelsePrinter.hvbox)p(fun()->print_styledctxPunctuation"(";Printer.indentpctx.indent_level(fun()->List.iteri(funiv->ifi>0thenPrinter.spacep();format_sexpin_block(depth+1)(i=0)ctxv)l);Printer.cutp();print_triviactxtrivia.within;Printer.boxp(fun()->print_styledctxPunctuation")";print_triviactxtrivia.after))|Block{l;transparent;loc;bk}->(lettrivia=get_triviactxlocinletindent=iffirstthenctx.indent_level-1else0inprint_triviactxtrivia.before;letrender()=List.iteri(funiv->ifi>0thenPrinter.spacep();format_sexp(in_block||nottransparent)depth(first&&i=0)ctxv)l;print_triviactxtrivia.within;print_triviactxtrivia.afterinmatchbkwith|`Hv->Printer.hvboxp~indentrender|`Hov->Printer.hovboxp~indentrender|`Box->(if(notin_block)&&transparent&&ctx.format=ExpansivethenPrinter.vboxelsePrinter.box)p~indentrender)|Vertical_block(loc,l)->lettrivia=get_triviactxlocin(* Printed before the box opens so a comment leading the first element
merges with the line break before the block instead of doubling it
into a blank line. *)print_triviactxtrivia.before;Printer.vboxp(fun()->List.iteri(funiv->ifi>0thenPrinter.spacep();format_sexpin_blockdepthfalsectxv)l;print_triviactxtrivia.within;print_triviactxtrivia.after)|Structured_block(loc,l)->lettrivia=get_triviactxlocinprint_triviactxtrivia.before;Printer.vboxp(fun()->letlen=List.lengthlinList.iteri(funis->matchswith|Delimiterd->ifi>0thenPrinter.newlinep();ifi=len-1thenprint_triviactxtrivia.within;Printer.boxp(fun()->format_sexpin_blockdepthfalsectxd;print_triviactxtrivia.after)|Contents[]->()|Contentsl->Printer.indentpctx.indent_level(fun()->Printer.newlinep();Printer.vboxp(fun()->List.iteri(funiv->ifi>0thenPrinter.newlinep();format_sexpin_blockdepthfalsectxv)l)))l)(* The dry pass that feeds {!Trivia.associate}'s [only] set needs just the set
of locations [format_sexp] would look up. [format_sexp] visits every node of
the [sexp] tree exactly once and probes each node's [loc] via [get_trivia], so
that set is precisely the [loc] fields present in the tree. Walk the tree to
gather them directly, skipping the whole layout engine a discarded
[format_sexp] run would otherwise drive — the dry pass used to be nearly as
costly as the real emit. *)letreccollect_locssets=letadd=functionSomeloc->Trivia.marksetloc|None->()inmatchswith|Atom{loc;_}->addloc|List(loc,l)|Vertical_block(loc,l)->addloc;List.iter(collect_locsset)l|Block{loc;l;_}->addloc;List.iter(collect_locsset)l|Structured_block(loc,l)->addloc;List.iter(function|Delimiterd->collect_locssetd|Contentsl->List.iter(collect_locsset)l)lletatom~style?locs=Atom{loc;len=None;style;s}letkeyword?locs=atom~style:Keyword?locsletinstruction?locs=atom~style:Instruction?locslettype_?locs=atom~style:Type?locsletlist?locl=List(loc,l)letblock?loc?(transparent=false)?(bk=`Box)l=Block{loc;l;transparent;bk}letstructured_block?locl=Structured_block(loc,l)letoptionfx=matchxwithNone->[]|Somex->fxletu32~style?loci=atom~style?loc(Uint32.to_stringi)letu64?loci=atom?loc(Uint64.to_stringi)letescape_string=Wax_utils.Unicode.escape_string(* The source text of a [$id] and its terminal width: a plain identifier is
written [$x], anything else in the quoted [$"…"] form with escaping. *)letid_partsx=ifLexer.is_valid_identifierxthen(Wax_utils.Unicode.terminal_widthx+1,"$"^x)elseleti,s=escape_stringxin(i+3,"$\""^s^"\"")letid_stringx=snd(id_partsx)letid?(style=Identifier)?locx=letlen,s=id_partsxinAtom{loc;style;len=Somelen;s}letopt_id=option(funi->[id~loc:i.Ast.infoi.Ast.desc])letindex?(style=Identifier)x=letloc=x.Ast.infoinmatchx.descwithNumi->u32~style~loci|Ids->id~style~locs(*** Type printing ***)letheaptype(ty:heaptype)=matchheaptype_keywordtywith|Somekw->type_kw|None->(matchtywith|Typet->indext|Exactt->list[type_"exact";indext]|_->assertfalse)letreftype{nullable;typ}=match(nullable,typ)with|true,Func->type_"funcref"|true,NoFunc->type_"nullfuncref"|true,Extern->type_"externref"|true,NoExtern->type_"nullexternref"|true,Any->type_"anyref"|true,Eq->type_"eqref"|true,I31->type_"i31ref"|true,Struct->type_"structref"|true,Array->type_"arrayref"|true,None_->type_"nullref"|true,Exn->type_"exnref"|true,NoExn->type_"nullexnref"|true,Cont->type_"contref"|true,NoCont->type_"nullcontref"|_->letr=[heaptypetyp]inlist(type_"ref"::(ifnullablethentype_"null"::relser))letvaltype(t:valtype)=matchtwith|I32->type_"i32"|I64->type_"i64"|F32->type_"f32"|F64->type_"f64"|V128->type_"v128"|Refty->reftypetyletpackedtypet=matchtwithI8->type_"i8"|I16->type_"i16"letmake_list~kind?(always=false)namefl=if(notalways)&&l=[]then[]else[list(kindname::fl)]letvaltype_listnametl=make_list~kind:keywordname(funtl->List.mapvaltypetl)tlletfunctype{params;results}=letparams=Array.to_listparamsinletparams_sexp=ifList.for_all(funp->fstp.Ast.desc=None)paramsthenvaltype_list"param"(List.map(funp->sndp.Ast.desc)params)else(* Anchor each [(param …)] at the parameter's own location (the name is
anchored separately via [opt_id]), so a trailing comment attaches to
the parameter. *)List.map(funp->leti,t=p.Ast.descinlist~loc:p.Ast.info(keyword"param"::(opt_idi@[valtypet])))paramsinparams_sexp@valtype_list"result"(Array.to_listresults)letstoragetypetyp=matchtypwithValuetyp->valtypetyp|Packedtyp->packedtypetypletmut_typef{mut;typ}=ifmutthenlist[keyword"mut";ftyp]elseftypletfieldtypetyp=mut_type(funt->storagetypet)typletglobaltypetyp=mut_type(funt->valtypet)typletcomptype(typ:comptype)=matchtypwith|Functy->list(keyword"func"::functypety)|Structl->list(keyword"struct"::List.map(funfld->letnm,f=fld.Ast.descinlist~loc:fld.Ast.info(keyword"field"::(opt_idnm@[fieldtypef])))(Array.to_listl))|Arrayty->list[keyword"array";fieldtypety]|Contidx->list[keyword"cont";indexidx]lettypeuseidx=[list[keyword"type";indexidx]]lettypeuse'(idx,typ)=optiontypeuseidx@optionfunctypetypletblocktype=option@@funt->matchtwith|Valtypet->[list[keyword"result";valtypet]]|Typeuset->typeuse'tletaddress_typeat=matchatwith`I32->[]|`I64->[keyword"i64"]letlimits{Ast.desc={mi;ma;address_type=at;page_size_log2;shared};_}=address_typeat@(u64~style:Constantmi::option(funi->[u64~style:Constanti])ma)@(ifsharedthen[keyword"shared"]else[])@matchpage_size_log2with|None->[]|Somep->[list[keyword"pagesize";u64~style:Constant(Uint64.of_int64(Int64.shift_left1Lp));];]lettabletype{limits=l;reftype=typ}=limitsl@[reftypetyp](*** Operators, immediates, and SIMD ***)letquoted_strings=letloc=s.Ast.infoinleti,s=escape_strings.Ast.descinAtom{loc=Someloc;style=String;len=Some(i+2);s="\""^s^"\""}letexportsl=List.map(funname->list[keyword"export";quoted_stringname])llettype_prefixopnm=(matchopwith|I32_->"i32."|I64_->"i64."|F32_->"f32."|F64_->"f64.")^nmletsignageop(s:signage)=op^matchswithSigned->"_s"|Unsigned->"_u"letsizesz=matchszwith`F32->"f32"|`F64->"f64"|`I32->"i32"|`I64->"i64"letint_un_opwidthop=matchopwith|Clz->"clz"|Ctz->"ctz"|Popcnt->"popcnt"|Eqz->"eqz"|Trunc(sz,s)->signage("trunc_"^sizesz)s|TruncSat(sz,s)->signage("trunc_sat_"^sizesz)s|Reinterpret->"reinterpret_f"^width|ExtendSsz->(matchszwith|`_8->"extend8_s"|`_16->"extend16_s"|`_32->"extend32_s")letint_bin_op_(op:int_bin_op)=matchopwith|Add->"add"|Sub->"sub"|Mul->"mul"|Divs->signage"div"s|Rems->signage"rem"s|And->"and"|Or->"or"|Xor->"xor"|Shl->"shl"|Shrs->signage"shr"s|Rotl->"rotl"|Rotr->"rotr"|Eq->"eq"|Ne->"ne"|Lts->signage"lt"s|Gts->signage"gt"s|Les->signage"le"s|Ges->signage"ge"sletfloat_un_opszop=matchopwith|Neg->"neg"|Abs->"abs"|Ceil->"ceil"|Floor->"floor"|Trunc->"trunc"|Nearest->"nearest"|Sqrt->"sqrt"|Convert(`I32,s)->signage"convert_i32"s|Convert(`I64,s)->signage"convert_i64"s|Reinterpret->"reinterpret_i"^szletfloat_bin_op_op=matchopwith|Add->"add"|Sub->"sub"|Mul->"mul"|Div->"div"|Min->"min"|Max->"max"|CopySign->"copysign"|Eq->"eq"|Ne->"ne"|Lt->"lt"|Gt->"gt"|Le->"le"|Ge->"ge"letselecti32i64f32f64_op=matchopwith|I32x->i32"32"x|I64x->i64"64"x|F32x->f32"32"x|F64x->f64"64"xletmemidxi=ifi.Ast.desc=NumUint32.zerothen[]else[indexi]letmemargalign'{offset;align}=(ifoffset=Uint64.zerothen[]else[atom~style:Attribute("offset="^Uint64.to_stringoffset)])@ifalign=align'then[]else[atom~style:Attribute(Printf.sprintf"align="^Uint64.to_stringalign)]letvec_shape=function|I8x16->"i8x16"|I16x8->"i16x8"|I32x4->"i32x4"|I64x2->"i64x2"|F32x4->"f32x4"|F64x2->"f64x2"letvec_tern_opop=matchopwith|VecRelaxedMAdd_->"relaxed_madd"|VecRelaxedNMAdd_->"relaxed_nmadd"|VecRelaxedLaneSelect_->"relaxed_laneselect"|VecRelaxedDotAdd->"relaxed_dot_i8x16_i7x16_add_s"letvec_const{Wax_utils.V128.shape;components}=keyword(matchshapewith|I8x16->"i8x16"|I16x8->"i16x8"|I32x4->"i32x4"|I64x2->"i64x2"|F32x4->"f32x4"|F64x2->"f64x2")::List.map(atom~style:Constant)components(* One data-segment element (WAT numeric-values proposal): a quoted byte string,
a typed numeric run [(i16 -1 2)], or a run of [v128] constants. *)letdata_elem_sexpe=matche.Ast.descwith|Strs->quoted_string{ewithAst.desc=s}|Numlist(ty,l)->list(storagetypety::List.map(atom~style:Constant)l)|V128listvs->list(keyword"v128"::List.concat_mapvec_constvs)letvec_load_op_nat_align=function|Load128->16|Load8x8S|Load8x8U|Load16x4S|Load16x4U|Load32x2S|Load32x2U|Load64Zero->8|Load32Zero->4letvec_lane_op_nat_align=function|`I8->1|`I16->2|`I32->4|`I64->8letnum_type_nat_align=functionNumI32|NumF32->4|NumI64|NumF64->8letstorage_type_nat_align=function`I8->1|`I16->2|`I32->4letcatchesl=List.map(func->matchcwith|Catch(x,l)->list[keyword"catch";indexx;indexl]|CatchRef(x,l)->list[keyword"catch_ref";indexx;indexl]|CatchAlll->list[keyword"catch_all";indexl]|CatchAllRefl->list[keyword"catch_all_ref";indexl])lleton_clausesl=List.map(func->matchcwith|OnLabel(x,l)->list[keyword"on";indexx;indexl]|OnSwitchx->list[keyword"on";indexx;keyword"switch"])lletcmp_op_string(op:Ast.cmp_op)=matchopwith|Eq->"="|Ne->"<>"|Lt->"<"|Gt->">"|Le->"<="|Ge->">="letreccond_doc(c:Ast.cond)=matchcwith|Cond_varv->id~style:Annotation~loc:v.Ast.infov.Ast.desc|Cond_strings->leti,str=escape_strings.Ast.descinAtom{loc=Somes.Ast.info;style=Annotation;len=Some(i+2);s="\""^str^"\"";}|Cond_version(a,b,c)->list(List.map(funn->atom~style:Annotation(string_of_intn))[a;b;c])|Cond_andl->list(atom~style:Annotation"and"::List.mapcond_docl)|Cond_orl->list(atom~style:Annotation"or"::List.mapcond_docl)|Cond_note->list[atom~style:Annotation"not";cond_doce]|Cond_cmp(op,a,b)->list[atom~style:Annotation(cmp_op_stringop);cond_doca;cond_docb](* A [metadata.code.*] annotation's raw-byte payload, escaped as a string
literal. *)lethint_payloads=leti,s=escape_stringsinAtom{loc=None;style=Annotation;len=Some(i+2);s="\""^s^"\""}lethint_annotnamepayload=list(atom~style:Annotation("@metadata.code."^name)::payload)(* Branch-hinting proposal: the [(@metadata.code.branch_hint "\00"|"\01")]
annotation that prefixes a hinted conditional branch. *)letbranch_hint_annotation(likely:bool)=hint_annot"branch_hint"[hint_payload(iflikelythen"\001"else"\000")](* Compilation-hints proposal: [(@metadata.code.instr_freq …)]. The reserved
values print as their keywords. Otherwise the byte stands for an
executions-per-call ratio, printed as [(freq n)] when that ratio is a whole
number — the common case, a hot instruction — and as the raw byte otherwise,
because a fractional ratio's shortest decimal need not re-encode to the same
byte and the hint must round-trip exactly. *)letinstr_freq_annotation(b:Hints.freq)=letpayload=ifb=Hints.never_optthen[list[atom~style:Annotation"never_opt"]]elseifb=Hints.always_optthen[list[atom~style:Annotation"always_opt"]]elsematchHints.ratio_of_freqbwith|SomerwhenFloat.is_integerr&&r>=1.->[list[atom~style:Annotation"freq";atom~style:Constant(Printf.sprintf"%.0f"r);];]|_->[hint_payload(Hints.freq_payloadb)]inhint_annot"instr_freq"payload(* Compilation-hints proposal: [(@metadata.code.call_targets (target $f 0.73) …)].
Always the structured form: the raw-byte spelling can only carry a numeric
index, so it could not print a target the source named. *)letcall_targets_annotationtargets=hint_annot"call_targets"(List.map(fun(idx,pct)->list[atom~style:Annotation"target";(* Printed without its source span: an annotation payload must not
be a trivia anchor, or a comment sitting before the annotation
would attach inside this group and split it across lines. *)(matchidx.Ast.descwith|Numi->u32~style:Identifieri|Ids->id~style:Identifiers);atom~style:Constant(Printf.sprintf"%g"(float_of_intpct/.100.));])targets)(* Compilation-hints proposal:
[(@metadata.code.compilation_priority (priority 1) (optimization 10))], the
one hint of the family that belongs to a function rather than an instruction.
Printed inside [(func …)] after the type use, which is the offset-0 position
the section keys it under. Always the structured form; [run_once] prints as its
keyword. *)letcompilation_priority_annotations(p:Hints.priorityoption)=Option.to_list(Option.map(fun({compilation;optimization}:Hints.priority)->hint_annot"compilation_priority"(list[atom~style:Annotation"priority";atom~style:Constant(string_of_intcompilation);]::Option.to_list(Option.map(funo->ifo=Hints.run_oncethenlist[atom~style:Annotation"run_once"]elselist[atom~style:Annotation"optimization";atom~style:Constant(string_of_into);])optimization)))p)(* Branch-hinting / compilation-hints proposals: the [(@metadata.code.…)]
annotations an instruction's hints print as, in section order. *)lethint_annotations(h:_Hints.t)=Option.to_list(Option.map(fun(b:boolHints.hint)->branch_hint_annotationb.value)h.Hints.branch)@Option.to_list(Option.map(fun(f:Hints.freqHints.hint)->instr_freq_annotationf.value)h.Hints.freq)@Option.to_list(Option.map(fun(l:_Hints.hint)->call_targets_annotationl.value)h.Hints.targets)(*** The instruction printer ***)(* A hinted instruction prints its annotations before itself. A block-form [if]
keeps its own multi-line layout, with the annotation on its own line above it;
an inline [br_if]/[br_on_*] stays on one line with the annotation. A folded
group takes its annotation outside the parentheses, which the [Folded] arm of
[instr_unhinted] handles for itself. *)letrecinstri=matchhint_annotationsi.hintswith|[]->instr_unhintedi|annots->(letloc=i.infoinletbare=instr_unhinted{iwithhints=Hints.none}inmatchi.descwith|If_->Vertical_block(Someloc,annots@[bare])|_->block~loc~transparent:true(annots@[bare]))andinstr_unhintedi=letloc=i.infoinmatchi.descwith|ExternConvertAny->instruction~loc"extern.convert_any"|AnyConvertExtern->instruction~loc"any.convert_extern"|Constop->block~loc[instruction(type_prefixop"const");atom~style:Constant(select(fun_i->i)(fun_i->i)(fun_i->i)(fun_i->i)(fun_i->i)op);]|UnOp(I32op)->instruction~loc("i32."^int_un_op"32"op)|UnOp(I64op)->instruction~loc("i64."^int_un_op"64"op)|UnOp(F32op)->instruction~loc("f32."^float_un_op"32"op)|UnOp(F64op)->instruction~loc("f64."^float_un_op"64"op)|BinOp(I32op)->instruction~loc("i32."^int_bin_op"32"op)|BinOp(I64op)->instruction~loc("i64."^int_bin_op"64"op)|BinOp(F32op)->instruction~loc("f32."^float_bin_op"32"op)|BinOp(F64op)->instruction~loc("f64."^float_bin_op"64"op)|Add128->instruction~loc"i64.add128"|Sub128->instruction~loc"i64.sub128"|MulWides->instruction~loc(signage"i64.mul_wide"s)|I32WrapI64->instruction~loc"i32.wrap_i64"|I64ExtendI32s->instruction~loc(signage"i64.extend_i32"s)|F32DemoteF64->instruction~loc"f32.demote_f64"|F64PromoteF32->instruction~loc"f64.promote_f32"|VecConstc->block~loc(instruction"v128.const"::vec_constc)|VecShufflelanes->block~loc(instruction"i8x16.shuffle"::List.init16(funi->atom~style:Constant(Int.to_string(Char.codelanes.[i]))))|VecExtract(op,s,lane)->block~loc[instruction~loc(vec_shapeop^".extract_lane"^matchswithSomes->signage""s|None->"");atom~style:Constant(Int.to_stringlane);]|VecReplace(op,lane)->block[instruction~loc(vec_shapeop^".replace_lane");atom~style:Constant(Int.to_stringlane);]|(VecSplat_|VecUnOp_|VecBinOp_|VecTest_|VecShift_|VecBitmask_)asdesc->(* Plain vector instructions share their WAT mnemonics with the lexer via
the Simd registry. *)instruction~loc(Option.get(Simd.wat_mnemonicdesc))|VecTernOpop->letshape_str=matchopwith|VecRelaxedMAdds|VecRelaxedNMAdds->(matchswith`F32->"f32x4"|`F64->"f64x2")|VecRelaxedLaneSelects->vec_shapes|VecRelaxedDotAdd->"i32x4"ininstruction~loc(shape_str^"."^vec_tern_opop)|VecBitselect->instruction~loc"v128.bitselect"|VecLoad(i,op,m)->block~loc(instruction(matchopwith|Load128->"v128.load"|Load8x8S->"v128.load8x8_s"|Load8x8U->"v128.load8x8_u"|Load16x4S->"v128.load16x4_s"|Load16x4U->"v128.load16x4_u"|Load32x2S->"v128.load32x2_s"|Load32x2U->"v128.load32x2_u"|Load32Zero->"v128.load32_zero"|Load64Zero->"v128.load64_zero")::(memidxi@memarg(Uint64.of_int(vec_load_op_nat_alignop))m))|VecStore(i,m)->block~loc(instruction"v128.store"::(memidxi@memarg(Uint64.of_int16)m))|VecLoadLane(i,op,m,lane)->block~loc(instruction(Printf.sprintf"v128.load%s_lane"(matchopwith|`I8->"8"|`I16->"16"|`I32->"32"|`I64->"64"))::(memidxi@memarg(Uint64.of_int(vec_lane_op_nat_alignop))m@[atom~style:Constant(Int.to_stringlane)]))|VecStoreLane(i,op,m,lane)->block~loc(instruction(Printf.sprintf"v128.store%s_lane"(matchopwith|`I8->"8"|`I16->"16"|`I32->"32"|`I64->"64"))::(memidxi@memarg(Uint64.of_int(vec_lane_op_nat_alignop))m@[atom~style:Constant(Int.to_stringlane)]))|VecLoadSplat(i,op,m)->block~loc(instruction(Printf.sprintf"v128.load%s_splat"(matchopwith|`I8->"8"|`I16->"16"|`I32->"32"|`I64->"64"))::(memidxi@memarg(Uint64.of_int(vec_lane_op_nat_alignop))m))|Load(i,m,sz)->block~loc(instruction(Printf.sprintf"%s.load"(matchszwith|NumI32->"i32"|NumI64->"i64"|NumF32->"f32"|NumF64->"f64"))::(memidxi@memarg(Uint64.of_int(num_type_nat_alignsz))m))|LoadS(i,m,sz,sz',s)->block~loc(instruction(signage(Printf.sprintf"%s.load%s"(matchszwith`I32->"i32"|`I64->"i64")(matchsz'with`I8->"8"|`I16->"16"|`I32->"32"))s)::(memidxi@memarg(Uint64.of_int(storage_type_nat_alignsz'))m))|Store(i,m,sz)->block~loc(instruction(Printf.sprintf"%s.store"(matchszwith|NumI32->"i32"|NumI64->"i64"|NumF32->"f32"|NumF64->"f64"))::(memidxi@memarg(Uint64.of_int(num_type_nat_alignsz))m))|StoreS(i,m,sz,sz')->block~loc(instruction(Printf.sprintf"%s.store%s"(matchszwith`I32->"i32"|`I64->"i64")(matchsz'with`I8->"8"|`I16->"16"|`I32->"32"))::(memidxi@memarg(Uint64.of_int(storage_type_nat_alignsz'))m))|Atomic(i,op,m)->block~loc(instruction(Atomics.nameop)::(memidxi@memarg(Uint64.of_int(1lslAtomics.natural_align_log2op))m))|AtomicFence->instruction~loc"atomic.fence"|MemorySizem->block~loc(instruction"memory.size"::memidxm)|MemoryGrowm->block~loc(instruction"memory.grow"::memidxm)|MemoryFillm->block~loc(instruction"memory.fill"::memidxm)|MemoryCopy(m,m')->block~loc(instruction"memory.copy"::(ifm.desc=NumUint32.zero&&m'.desc=NumUint32.zerothen[]else[indexm;indexm']))|MemoryInit(m,d)->block~loc(instruction"memory.init"::(memidxm@[indexd]))|DataDropd->block~loc[instruction"data.drop";indexd]|TableGetm->block~loc(instruction"table.get"::memidxm)|TableSetm->block~loc(instruction"table.set"::memidxm)|TableSizem->block~loc(instruction"table.size"::memidxm)|TableGrowm->block~loc(instruction"table.grow"::memidxm)|TableFillm->block~loc(instruction"table.fill"::memidxm)|TableCopy(m,m')->block~loc(instruction"table.copy"::(ifm.desc=NumUint32.zero&&m'.desc=NumUint32.zerothen[]else[indexm;indexm']))|TableInit(m,d)->block~loc(instruction"table.init"::(memidxm@[indexd]))|ElemDropd->block~loc[instruction"elem.drop";indexd]|LocalGeti->block~loc[instruction"local.get";indexi]|LocalTeei->block~loc[instruction"local.tee";indexi]|GlobalGeti->block~loc[instruction"global.get";indexi]|CallIndirect(id,typ)->block~loc(instruction"call_indirect"::((ifid.desc=NumUint32.zerothen[]else[indexid])@typeuse'typ))|ReturnCallIndirect(id,typ)->block~loc(instruction"return_call_indirect"::((ifid.desc=NumUint32.zerothen[]else[indexid])@typeuse'typ))|Callf->block~loc[instruction"call";indexf]|Selectt->block~loc(instruction"select"::option(funt->valtype_list"result"t)t)|RefFunci->block~loc[instruction"ref.func";indexi]|RefIsNull->block~loc[instruction"ref.is_null"]|RefAsNonNull->block~loc[instruction"ref.as_non_null"]|CallReft->block~loc[instruction"call_ref";indext]|RefI31->instruction~loc"ref.i31"|I31Gets->instruction~loc(signage"i31.get"s)|ArrayNewt->block~loc[instruction"array.new";indext]|ArrayNewDefaultt->block~loc[instruction"array.new_default";indext]|ArrayNewFixed(t,i)->block~loc[instruction"array.new_fixed";indext;u32~style:Constanti]|ArrayNewElem(i,i')->block~loc[instruction"array.new_elem";indexi;indexi']|ArrayNewData(typ,data)->block~loc[instruction"array.new_data";indextyp;indexdata]|ArrayInitData(i,i')->block~loc[instruction"array.init_data";indexi;indexi']|ArrayInitElem(i,i')->block~loc[instruction"array.init_elem";indexi;indexi']|ArrayGet(None,typ)->block~loc[instruction"array.get";indextyp]|ArrayGet(Somes,typ)->block~loc[instruction(signage"array.get"s);indextyp]|ArrayLen->instruction~loc"array.len"|ArrayCopy(i,i')->block~loc[instruction"array.copy";indexi;indexi']|ArrayFilli->block~loc[instruction"array.fill";indexi]|StructNewtyp->block~loc[instruction"struct.new";indextyp]|StructNewDefaulttyp->block~loc[instruction"struct.new_default";indextyp]|StructNewDesctyp->block~loc[instruction"struct.new_desc";indextyp]|StructNewDefaultDesctyp->block~loc[instruction"struct.new_default_desc";indextyp]|StructGet(None,typ,f)->block~loc[instruction"struct.get";indextyp;indexf]|StructGet(Somes,typ,f)->block~loc[instruction(signage"struct.get"s);indextyp;indexf]|RefCastty->block~loc[instruction"ref.cast";reftypety]|RefCastDescEqty->block~loc[instruction"ref.cast_desc_eq";reftypety]|RefGetDesci->block~loc[instruction"ref.get_desc";indexi]|RefTestty->block~loc[instruction"ref.test";reftypety]|RefEq->instruction~loc"ref.eq"|RefNullty->block~loc[instruction"ref.null";heaptypety]|If{label;typ;if_block;else_block}->structured_block~loc(Delimiter(block(instruction"if"::(opt_idlabel@blocktypetyp)))::Contents(List.mapinstrif_block.desc)::(ifelse_block.desc=[]then[Delimiter(instruction"end")]else[Delimiter(instruction"else");Contents(List.mapinstrelse_block.desc);Delimiter(instruction"end");]))|Drop->instruction~loc"drop"|LocalSeti->block~loc[instruction"local.set";indexi]|GlobalSeti->block~loc[instruction"global.set";indexi]|Block{label;typ;block=b}->structured_block~loc[Delimiter(block(instruction"block"::(opt_idlabel@blocktypetyp)));Contents(List.mapinstrb.desc);Delimiter(instruction"end");]|Loop{label;typ;block=b}->structured_block~loc[Delimiter(block(instruction"loop"::(opt_idlabel@blocktypetyp)));Contents(List.mapinstrb.desc);Delimiter(instruction"end");]|TryTable{label;typ;catches=c;block=b}->structured_block~loc[Delimiter(block(instruction"try_table"::(opt_idlabel@blocktypetyp)));Contents(catchesc@List.mapinstrb.desc);Delimiter(instruction"end");]|Try{label;typ;block=b;catches;catch_all}->structured_block~loc(Delimiter(block(instruction"try"::(opt_idlabel@blocktypetyp)))::Contents(List.mapinstrb.desc)::(List.flatten(List.map(fun(i,l)->[Delimiter(block[instruction"catch";indexi]);Contents(List.mapinstrl.Ast.desc);])catches)@(matchcatch_allwith|None->[]|Somec->[Delimiter(instruction"catch_all");Contents(List.mapinstrc.desc);])@[Delimiter(instruction"end")]))|Br_table(l,i)->block~loc(instruction"br_table"::List.map(funi->indexi)(l@[i]))|Bri->block~loc[instruction"br";indexi]|Br_ifi->block~loc[instruction"br_if";indexi]|Br_on_nulli->block~loc[instruction"br_on_null";indexi]|Br_on_non_nulli->block~loc[instruction"br_on_non_null";indexi]|Br_on_cast(i,ty,ty')->block~loc[instruction"br_on_cast";indexi;reftypety;reftypety']|Br_on_cast_fail(i,ty,ty')->block~loc[instruction"br_on_cast_fail";indexi;reftypety;reftypety']|Br_on_cast_desc_eq(i,ty,ty')->block~loc[instruction"br_on_cast_desc_eq";indexi;reftypety;reftypety']|Br_on_cast_desc_eq_fail(i,ty,ty')->block~loc[instruction"br_on_cast_desc_eq_fail";indexi;reftypety;reftypety';]|Return->instruction~loc"return"|Throwtag->block~loc[instruction"throw";indextag]|ThrowRef->block~loc[instruction"throw_ref"]|ContNewi->block~loc[instruction"cont.new";indexi]|ContBind(i,j)->block~loc[instruction"cont.bind";indexi;indexj]|Suspendi->block~loc[instruction"suspend";indexi]|Resume(i,clauses)->block~loc(instruction"resume"::indexi::on_clausesclauses)|ResumeThrow(i,j,clauses)->block~loc(instruction"resume_throw"::indexi::indexj::on_clausesclauses)|ResumeThrowRef(i,clauses)->block~loc(instruction"resume_throw_ref"::indexi::on_clausesclauses)|Switch(i,j)->block~loc[instruction"switch";indexi;indexj]|Nop->instruction~loc"nop"|Unreachable->instruction~loc"unreachable"|ArraySettyp->block~loc[instruction"array.set";indextyp]|StructSet(typ,i)->block~loc[instruction"struct.set";indextyp;indexi]|ReturnCallf->block~loc[instruction"return_call";indexf]|ReturnCallReftyp->block~loc[instruction"return_call_ref";indextyp](* A folded hinted branch puts the annotation *before* the folded group —
[(@…) (br_if …)] / [(@…) (if …)] — not inside it. The head is reprinted
without its hints so the group itself comes out plain. *)|Folded(head,l)whennot(Hints.is_emptyhead.hints)->block~loc~transparent:true(hint_annotationshead.hints@[instr_unhinted{desc=Folded({headwithhints=Hints.none},l);info=head.info;hints=Hints.none;};])|Folded({desc=If{label;typ;if_block;else_block};_},l)->(* Give each clause the location of its (then ...)/(else ...) group so
a comment trailing the clause attaches to it. *)letclause?(always=false)nameb=if(notalways)&&b.Ast.desc=[]then[]else[list~loc:b.Ast.info(instructionname::List.mapinstrb.Ast.desc);]inlist~loc(block~transparent:true(block(instruction"if"::(opt_idlabel@blocktypetyp))::List.mapinstrl)::(clause~always:true"then"if_block@clause"else"else_block))|Folded({desc=Block{label;typ;block=b};_},l)->assert(l=[]);list~loc(block(instruction"block"::(opt_idlabel@blocktypetyp))::List.mapinstrb.desc)|Folded({desc=Loop{label;typ;block=b};_},l)->assert(l=[]);list~loc(block(instruction"loop"::(opt_idlabel@blocktypetyp))::List.mapinstrb.desc)|Folded({desc=TryTable{label;typ;catches=c;block=b};_},l)->assert(l=[]);list~loc(block(instruction"try_table"::(opt_idlabel@blocktypetyp))::block(catchesc)::List.mapinstrb.desc)|Folded({desc=Try{label;typ;block=b;catches;catch_all};_},l)->assert(l=[]);list~loc(block(instruction"try"::(opt_idlabel@blocktypetyp))::list(instruction"do"::List.mapinstrb.desc)::(List.map(fun(i,l)->list(block[instruction"catch";indexi]::List.mapinstrl.Ast.desc))catches@matchcatch_allwith|None->[]|Somel->[list(instruction"catch_all"::List.mapinstrl.Ast.desc)]))|String(id,s)|Folded({desc=String(id,s);_},[])->list~loc(block(atom~style:Annotation"@string"::option(funid->[index~style:Annotationid])id)::List.map(funs->letloc=Somes.Ast.infoinleti,s=escape_strings.Ast.descinAtom{loc;style=Annotation;len=Some(i+2);s="\""^s^"\"";})s)|Charc|Folded({desc=Charc;_},[])->letn=Uchar.utf_8_byte_lengthcinletb=Bytes.createninignore(Bytes.set_utf_8_ucharb0c);list~loc[atom~style:Annotation"@char";(leti,s=escape_string(Bytes.to_stringb)inAtom{loc=None;style=Annotation;len=Some(i+2);s="\""^s^"\"";});]|If_annotation{cond;then_body;else_body}|Folded({desc=If_annotation{cond;then_body;else_body};_},[])->letclauseheadbody=list~loc:body.Ast.info(atom~style:Annotation("@"^head)::List.mapinstrbody.Ast.desc)inlist~loc(block[atom~style:Annotation"@if";cond_doccond]::clause"then"then_body::option(fune->[clause"else"e])else_body)|Folded(i,l)->list~loc[block~transparent:true(instri::List.mapinstrl)]letinstr_list_needs_vertical_layoutl=List.exists(fun(i:_Ast.Text.instr)->matchi.descwithFolded_->false|_->true)lletinline_instrsl=List.mapinstrlletinstrsl=matchlwith|[]->[]|_->letdocs=List.mapinstrlinifinstr_list_needs_vertical_layoutlthen[Vertical_block(None,docs)]elsedocs(*** Types, declarations, and module fields ***)letsubtype?loct=letid,{typ;supertype;final;descriptor;describes}=t.Ast.descinletloc=matchlocwithSome_->loc|None->Somet.Ast.infoin(* [(describes $o)] / [(descriptor $d)] clauses precede the composite type. *)letclauses=option(funi->[list[keyword"describes";indexi]])describes@option(funi->[list[keyword"descriptor";indexi]])descriptoriniffinal&&Option.is_nonesupertypethenlist?loc(block(keyword"type"::opt_idid)::(clauses@[block[comptypetyp]]))elselist?loc[block(keyword"type"::opt_idid);list[block(block(keyword"sub"::((iffinalthen[keyword"final"]else[])@option(funi->[indexi])supertype))::(clauses@[comptypetyp]));];]letfundecl(idx,typ)=optiontypeuseidx@option(fun{params;results}->ifparams=[||]&&results=[||]then[]else[block((ifArray.for_all(funp->fstp.Ast.desc=None)paramsthenmake_list~kind:keyword"param"(funtl->List.mapvaltypetl)(Array.to_list(Array.map(funp->sndp.Ast.desc)params))elseArray.to_list(Array.map(funp->leti,t=p.Ast.descinlist~loc:p.Ast.info(keyword"param"::(opt_idi@[valtypet])))params))@valtype_list"result"(Array.to_listresults));])typletexprnamee=matchewith|[({desc=Folded_;_}asi)]->instri|_->list(keywordname::inline_instrse)letfunction_indices(lst:_Ast.Text.exprlist)=letextracti=matchiwith|[({desc=RefFuncidx;_}|{desc=Folded({desc=RefFuncidx;_},[]);_});]->Someidx|_->NoneinifList.for_all(funi->extracti<>None)lstthenSome(List.filter_mapextractlst)elseNone(* The [(kind …)] descriptor s-expression shared by a plain import and a compact
group item, e.g. [(func $id (type 0))] / [(global i32)]. *)letimport_desc_blockid(desc:importdesc)=letkind,typ=matchdescwith|Func{exact;typ}->("func",ifexactthen[list(type_"exact"::fundecltyp)]elsefundecltyp)|Globalty->("global",[globaltypety])|Tagtyp->("tag",fundecltyp)|Memoryl->("memory",limitsl)|Tablety->("table",tabletypety)inblock(keywordkind::(opt_idid@typ))letrecmodulefieldf=letloc=f.Ast.infoinmatchf.Ast.descwith(* Carry the field location (which spans the whole type definition) so a
leading comment or blank line attaches before the type rather than to its
inner name — the array element itself is built with a dummy location. *)|Types[|t|]->subtype~loct|Typesl->list~loc(keyword"rec"::List.mapsubtype(Array.to_listl))|Func{id;typ;locals;instrs=i;exports=e;priority}->letlocal_sexpe=letnm,t=e.Ast.descin(* The hovbox lets a comment before any local break correctly while the
locals stay packed. *)list~loc:e.Ast.info(keyword"local"::(opt_idnm@[valtypet]))inletlocals_block=matchlocalswith|[]->[]|_->[block~bk:`Hov(List.maplocal_sexplocals)]inlist~loc(block(keyword"func"::(opt_idid@exportse@fundecltyp))::(compilation_priority_annotationspriority@locals_block@instrsi))|Import{module_;name;id;desc;exports=e}->(matchewith|[]->list~loc[block[keyword"import";quoted_stringmodule_;quoted_stringname];list[import_desc_blockiddesc];]|_->letkind,typ=matchdescwith|Func{exact;typ}->("func",ifexactthen[list(type_"exact"::fundecltyp)]elsefundecltyp)|Globalty->("global",[globaltypety])|Tagtyp->("tag",fundecltyp)|Memoryl->("memory",limitsl)|Tablety->("table",tabletypety)inlist~loc(block(keywordkind::(opt_idid@exportse@[list[keyword"import";quoted_stringmodule_;quoted_stringname;];]))::typ))(* compact-import-section: [(import "m" (item "n" (kind …)) …)] with a type per
item, and [(import "m" (item "n") … (kind …))] with one shared trailing
type on name-only items. *)|Import_group1{module_;items}->list~loc(block[keyword"import";quoted_stringmodule_]::List.map(fun(name,id,desc)->list[keyword"item";quoted_stringname;list[import_desc_blockiddesc];])items)|Import_group2{module_;desc;items}->list~loc(block[keyword"import";quoted_stringmodule_]::List.map(funname->list[keyword"item";quoted_stringname])items@[list[import_desc_blockNonedesc]])|Global{id;typ;init;exports=e}->list~loc(block(keyword"global"::(opt_idid@exportse@[globaltypetyp]))::instrsinit)|Tag{id;typ;exports=e}->list~loc[block(keyword"tag"::(opt_idid@exportse@fundecltyp))]|Data{id;init;mode}->lethead=keyword"data"::(opt_idid@matchmodewith|Passive->[]|Active(i,e)->(ifi.desc=NumUint32.zerothen[]else[list[keyword"memory";indexi]])@[expr"offset"e])inlist~loc(blockhead::List.mapdata_elem_sexpinit)|Startidx->list~loc[keyword"start";indexidx]|Memory{id;limits=l;init;exports=e}->lethead=keyword"memory"::(opt_idid@exportse@matchinitwith|None->limitsl|Some_->address_typel.desc.address_type)inlist~loc(blockhead::(matchinitwith|None->[]|Someinit->[list(keyword"data"::List.mapdata_elem_sexpinit)]))|Table{id;typ;init;exports=e}->list~loc(block(keyword"table"::(opt_idid@exportse@matchinitwith|Init_default|Init_expr_->tabletypetyp|Init_segment_->address_typetyp.limits.desc.address_type@[reftypetyp.reftype]))::(matchinitwith|Init_default->[]|Init_expri->instrsi|Init_segmentseg->[list(keyword"elem"::(matchfunction_indicessegwith|Somelst->List.mapindexlst|None->List.map(fune->expr"item"e)seg));]))|Export{name;kind;index=i}->list~loc[keyword"export";quoted_stringname;list[keyword(matchkindwith|Func->"func"|Memory->"memory"|Table->"table"|Tag->"tag"|Global->"global");indexi;];]|Elem{id;typ;init;mode}->letlst=(* The [func] shorthand denotes element type [(ref func)] (non-null,
matching the [ref.func] elements), not the nullable [funcref]. It
may therefore only be used for a non-nullable [Func] type;
abbreviating a [funcref] segment would silently drop its
nullability. *)matchtypwith|{nullable=false;typ=Func}->function_indicesinit|_->Noneinlist~loc[block(keyword"elem"::(opt_idid@(matchmodewith|Passive->[]|Active(idx,ofs)->(ifidx.desc=NumUint32.zerothen[]else[list[keyword"table";indexidx]])@[expr"offset"ofs]|Declare->[keyword"declare"])@[(iflst=Nonethenreftypetypelsekeyword"func")]));block(matchlstwith|Somelst->List.mapindexlst|_->List.map(fune->expr"item"e)init);]|String_global{id=i;typ;init}->list~loc(block(atom~style:Annotation"@string"::id~style:Annotationi.Ast.desc::option(funid->[index~style:Annotationid])typ)::List.map(funs->leti,s=escape_strings.Ast.descinAtom{loc=None;style=Annotation;len=Some(i+2);s="\""^s^"\"";})init)|Feature_annotationname->leti,s=escape_stringname.Ast.descinlist~loc[atom~style:Annotation"@feature";Atom{loc=None;style=Annotation;len=Some(i+2);s="\""^s^"\"";};]|Module_if_annotation{cond;then_fields;else_fields}->letclauseheadfields=list~loc:fields.Ast.info(atom~style:Annotation("@"^head)::List.mapmodulefieldfields.Ast.desc)inlist~loc(block[atom~style:Annotation"@if";cond_doccond]::clause"then"then_fields::option(fune->[clause"else"e])else_fields)(*** Entry points ***)(* The pretty-printer's intermediate [sexp] tree, laid out once. Building it for
a large module allocates heavily, so a caller that needs both the dry
trivia-collection pass and the real emit builds it a single time with
{!prepare} and drives both passes off the result — rather than rebuilding it
for each, which used to dominate the WAT output path. [top_depth] is the
initial [format_sexp] depth (1 for an unnamed [(module …)] laid out as a
[Vertical_block], 0 otherwise). *)typedocument={sexp:sexp;top_depth:int}letprepare(id,fields)=Wax_utils.Debug.timed"layout"@@fun()->letsexp=(* Top-level fields are laid out strictly one per line. *)ifid=NonethenVertical_block(None,List.mapmodulefieldfields)elselist(block~transparent:true(keyword"module"::opt_idid)::List.mapmodulefieldfields)in{sexp;top_depth=(ifid=Nonethen1else0)}(* Dry pass: record the looked-up locations without laying anything out. *)letcollect{sexp;_}set=collect_locssetsexpletemit?(color=Auto)?out_channel?(tail=[])printer~trivia{sexp;top_depth}=Wax_utils.Debug.timed"output"@@fun()->letuse_color=should_use_color~color~out_channelinlettheme=get_themeuse_colorinletctx={base=Styled.create~printer~theme~trivia();format=Hybrid;indent_level=2;}informat_sexpfalsetop_depthfalsectxsexp;(* Comments owned by no location (trailing comments, or the whole file for an
empty module) are printed last so they are not dropped. Blank lines at the
very end are dropped (end-of-file whitespace), but blank lines separating
tail comments are kept. *)lettail=Trivia.drop_trailing_blank_linestailinprint_triviactxtailletmodule_?color?out_channel?(tail=[])printer~triviam=emit?color?out_channel~tailprinter~trivia(preparem)letinstrprinteri=letuse_color=should_use_color~color:Auto~out_channel:(Somestderr)inlettheme=get_themeuse_colorinformat_sexpfalse2false{base=Styled.create~printer~theme~trivia:(Wax_utils.Trivia.empty())();format=Compact;indent_level=2;}(instri)(* A type definition rendered to a plain (uncoloured) one-line-ish string, for
the editor to show on hover over a type identifier. *)letsubtype_stringt=String.trim(Printer.run_string(funprinter->format_sexpfalse2false{base=Styled.create~printer~theme:(get_themefalse)~trivia:(Wax_utils.Trivia.empty())();format=Compact;indent_level=2;}(subtypet)))