123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064(*ZZZZ
Should use string_as for comments
*)openWax_utils.ColorsopenAstletindent_level=4(* Target line width for Wax output, matching the Rust Style Guide's default
([max_width = 100]); WebAssembly text output keeps the printer's own default.
Passed at every [Printer.run] that renders a Wax module to a real
formatter. *)letwidth=100(*** Printer primitives ***)letget_themeuse_color=ifuse_colorthenwax_themeelseno_colortype'infoctx={base:Wax_utils.Styled_printer.t;(* Extract a source location from a node's annotation, to look its trivia up.
[fun _ -> None] when printing typed ASTs for diagnostics (no trivia). *)locate:'info->locationoption;}letprint_styledppstyle?(len=None)text=Wax_utils.Styled_printer.print_styledpp.basestyle~lentextletboxpp?indentf=Wax_utils.Printer.boxpp.base.printer?indentflethvboxpp?indentf=Wax_utils.Printer.hvboxpp.base.printer?indentflethboxppf=Wax_utils.Printer.hboxpp.base.printerfletindentppif=Wax_utils.Printer.indentpp.base.printerifletspacepp()=Wax_utils.Printer.spacepp.base.printer()letcutpp()=Wax_utils.Printer.cutpp.base.printer()letnewlinepp()=Wax_utils.Printer.newlinepp.base.printer()letpunctuationpps=print_styledppPunctuationsletoperatorpps=print_styledppOperators(* A declaration terminator [;], held past a deferred trailing comment so it
sits before the comment ([const x = v; // c]) rather than dangling on its own
line after it — as the block-statement [;] and list [,] separators already
do. *)letsemicolonpp=Wax_utils.Printer.with_held_eolpp.base.printer(fun()->punctuationpp";")letidentifierpps=print_styledppIdentifier~len:(Some(Wax_utils.Unicode.terminal_widths))sletconstantpps=print_styledppConstantsletkeywordpps=print_styledppKeywordslettype_pps=print_styledppTypesletstringpp?lens=print_styledppString?lensletattributepps=print_styledppAttributes(* Branch-hinting proposal: the [#[likely]]/[#[unlikely]] prefix on a hinted
conditional branch. *)letbranch_hint_attrpplikely=attributepp(iflikelythen"#[likely]"else"#[unlikely]");spacepp()(* Comment preservation: emit the trivia (comments, blank lines) the lexer
collected, looked up by AST-node location. The rendering logic is shared with
the WebAssembly printer in [Wax_utils.Trivia]. *)letprint_triviapplst=Wax_utils.Styled_printer.print_triviapp.baselstletget_triviapp(loc:locationoption)=Wax_utils.Styled_printer.get_triviapp.baselocletatomic_nodepp(loc:locationoption)f=Wax_utils.Styled_printer.atomic_nodepp.baselocfletwith_stylectxstylef=Wax_utils.Styled_printer.with_stylectx.basestylefletlist?(sep=space)fppl=matchlwith|[]->()|[x]->fppx|x::xs->fppx;List.iter(funx->seppp();fppx)xsletlist_commasepfppl=list~sep:(funpp()->(* Hold any deferred trailing comment so the comma prints on the comment's
line, ahead of it. *)Wax_utils.Printer.with_held_eolpp.base.printer(fun()->punctuationpp",");spacepp())fppl(* A trailing comma after the last element of [l], emitted only when the
enclosing box wraps across lines (rustfmt style). Skipped when the last
element carries a trailing comment: a comma there would push the comment off
the element and change where it re-attaches on a reparse (breaking
idempotence), so the comment-less last element keeps its layout. *)lettrailing_commappl=ifl<>[]&¬(Wax_utils.Printer.has_pending_eolpp.base.printer)thenWax_utils.Printer.if_brokenpp.base.printer(fun()->punctuationpp",")letlist_commasep_trailingfppl=list_commasepfppl;trailing_commapplletprint_paren_listfppl=punctuationpp"(";boxpp(fun()->list_commasepfppl);punctuationpp")"(* A Rust-style parenthesised list: it stays on one line if it fits, otherwise
[(] keeps the preceding token company and the elements break one per line,
indented one level, with the closing [)] back at the opening column — never
the Lisp-like [(] on its own line. The caller's enclosing [hvbox] makes the
choice all-or-nothing. *)letprint_arg_listfppl=punctuationpp"(";(matchlwith|[]->()|_->indentppindent_level(fun()->cutpp();list_commasep_trailingfppl);cutpp());punctuationpp")"(*** Type printing ***)letheaptypepp(t:heaptype)=matchheaptype_keywordtwith|Somekw->type_ppkw|None->(matchtwithTypes|Exacts->type_pps.desc|_->assertfalse)letreftypepp{nullable;typ}=(* The [!] exact marker sits between the [&]/[&?] sigil and the type name. *)punctuationpp(match(nullable,typ)with|true,Exact_->"&?!"|false,Exact_->"&!"|true,_->"&?"|false,_->"&");heaptypepptypletrecvaltypeppt=matchtwith|I32->type_pp"i32"|I64->type_pp"i64"|F32->type_pp"f32"|F64->type_pp"f64"|V128->type_pp"v128"|Reft->reftypepptandtuplealways_parenppl=matchlwith|[t]whennotalways_paren->valtypeppt|_->print_paren_listvaltypepplletsimple_patppp=matchpwith|Somex->(* Anchor at the identifier's own location, so a comment trailing the name
(e.g. before a parameter's [: type]) attaches to it. *)atomic_nodepp(Somex.info)(fun()->identifierppx.desc)|None->operatorpp"_"letprint_key_valueppkeyval_printervalue=boxpp~indent:indent_level(fun()->identifierppkey;punctuationpp":";spacepp();val_printerppvalue)letprint_typed_patpp(pat,opt_typ)=boxpp~indent:indent_level(fun()->simple_patpppat;Option.iter(funt->punctuationpp":";spacepp();valtypeppt)opt_typ)letraw_functypepp{params;results}=print_arg_list(funppp->letid,t=p.descin(* Anchor trivia at the whole parameter, so a trailing comment attaches to
it — named or not. *)atomic_nodepp(Somep.info)(fun()->matchidwith|None->valtypeppt|Some_->print_typed_patpp(id,Somet)))pp(Array.to_listparams);ifresults<>[||]then(* Keep [-> Ret] glued to the closing [)] so the parameter list, not the
arrow, is what breaks when the signature overflows. *)hboxpp(fun()->spacepp();operatorpp"->";spacepp();tuplefalsepp(Array.to_listresults))letfunctypeppty=boxpp~indent:indent_level(fun()->keywordpp"fn";raw_functypeppty)letblocktypepptyp=matchtypwith|{params=[||];results=[|ty|]}->valtypeppty|_->boxpp~indent:indent_level(fun()->raw_functypepptyp)letpackedtypeppt=type_pp(matchtwithI8->"i8"|I16->"i16")letstoragetypeppt=matchtwithValuet->valtypeppt|Packedt->packedtypeppt(* The bare name of a numeric-literal type suffix ([1i32] -> ["i32"]). *)letsuffix_string:Ast.storagetype->string=function|PackedI8->"i8"|PackedI16->"i16"|ValueI32->"i32"|ValueI64->"i64"|ValueF32->"f32"|ValueF64->"f64"|Value(V128|Ref_)->assertfalseletmuttypetpp{mut;typ}=ifmutthenboxpp~indent:indent_level(fun()->keywordpp"mut";spacepp();tpptyp)elsetpptypletfieldtype=muttypestoragetypeletcomptypepp(t:comptype)=matchtwith|Funct->functypeppt|Structl->(* The opening brace is printed in [subtype] *)indentppindent_level(fun()->spacepp();list_commasep(funppfield->(* A leading [..] inherits the supertype's fields; the parser puts
it first, so it prints as the first comma-separated item. *)ifAst.is_splice_fieldfieldthenpunctuationpp".."elseletnm,t=field.descin(* Look the field's trivia up by its own location, so a trailing
comment attaches to the whole field. *)atomic_nodepp(Somefield.info)(fun()->print_key_valueppnm.descfieldtypet))pp(Array.to_listl));spacepp();punctuationpp"}"|Arrayt->punctuationpp"[";boxpp(fun()->fieldtypeppt);punctuationpp"]"|Conts->type_pp"cont";spacepp();type_pps.descletsubtypeppfield=letnm,{typ;supertype;final;descriptor;describes}=field.descinatomic_nodepp(Somefield.info)@@fun()->hvboxpp(fun()->letis_struct=matchtypwithStruct_->true|_->falseinboxpp(fun()->keywordpp"type";spacepp();identifierppnm.desc;(matchsupertypewith|Somesupertype->punctuationpp":";spacepp();identifierppsupertype.desc|None->());spacepp();punctuationpp"=";ifnotfinalthen(spacepp();keywordpp"open");(* custom-descriptors clauses, between [open] and the body. *)letclausekw=function|Some(id:ident)->spacepp();keywordppkw;spacepp();identifierppid.desc|None->()inclause"describes"describes;clause"descriptor"descriptor;ifis_structthen(spacepp();punctuationpp"{"));spacepp();comptypepptyp;punctuationpp";")letrectypeppt=matchArray.to_listtwith|[t]->subtypeppt|l->hvboxpp(fun()->boxpp(fun()->keywordpp"rec";spacepp();punctuationpp"{");indentppindent_level(fun()->spacepp();list~sep:spacesubtypeppl);spacepp();punctuationpp"}")(*** Operators and precedence ***)letbinopop=matchopwith|Add->"+"|Sub->"-"|Mul->"*"|DivNone->"/"|Div(SomeSigned)->"/s"|Div(SomeUnsigned)->"/u"|RemSigned->"%s"|RemUnsigned->"%u"|And->"&"|Or->"|"|Xor->"^"|Shl->"<<"|ShrSigned->">>s"|ShrUnsigned->">>u"|Eq->"=="|Ne->"!="|LtNone->"<"|Lt(SomeSigned)->"<s"|Lt(SomeUnsigned)->"<u"|GtNone->">"|Gt(SomeSigned)->">s"|Gt(SomeUnsigned)->">u"|LeNone->"<="|Le(SomeSigned)->"<=s"|Le(SomeUnsigned)->"<=u"|GeNone->">="|Ge(SomeSigned)->">=s"|Ge(SomeUnsigned)->">=u"letunopop=matchopwithNeg->"-"|Pos->"+"|Not->"!"typeprec=|Instruction|Branch|Assignement|Select|Comparison|LogicalOr|LogicalXor|LogicalAnd|Shift|Addition|Multiplication|Cast|UnaryPrefix|UnaryPostfix|CallAndFieldAccess|Atomletparenthesesexpectedactualppg=ifexpected>actualthen(punctuationpp"(";boxpp(fun()->g();(* Hold the closing [)] past a trailing comment on the last inner token,
so it hugs the expression ([(c == 108) // 'l']) instead of being
pushed onto its own line after the comment — as [;] and [,] do. *)Wax_utils.Printer.with_held_eolpp.base.printer(fun()->punctuationpp")")))elseg()letprec_opop=(* out, left, right *)matchopwith|Add|Sub->(Addition,Addition,Multiplication)|Mul|Div_|Rem_->(Multiplication,Multiplication,Cast)|And->(LogicalAnd,LogicalAnd,Shift)|Or->(LogicalOr,LogicalOr,LogicalXor)|Xor->(LogicalXor,LogicalXor,LogicalAnd)|Shl|Shr_->(Shift,Shift,Addition)|Gt_|Lt_|Ge_|Le_|Eq|Ne->(Comparison,LogicalOr,LogicalOr)(*** Instruction-printing helpers ***)letblock_labelpplabel=Option.iter(funlabel->identifierpp"'";identifierpplabel.desc;punctuationpp":";spacepp())labelletneed_blocktypebt=bt.params<>[||]||bt.results<>[||]letcasttypeppty=matchtywith|Valtypety->valtypeppty|Functype{nullable;sign}->punctuationpp(ifnullablethen"&?"else"&");functypeppsign|Signedtype{typ;signage;strict}->type_pp(Ast.format_signed_typetypsignagestrict)letbranch_instrinstrppnamelabeli=boxpp~indent:indent_level(fun()->keywordppname;spacepp();identifierpp"'";identifierpplabel.desc;Option.iter(funi->spacepp();instrBranchppi)i)letbranch_ref_instrinstrppnamelabeltyi=boxpp~indent:indent_level(fun()->keywordppname;spacepp();identifierpp"'";identifierpplabel.desc;spacepp();reftypeppty;spacepp();instrBranchppi)(* [ [?]descriptor(d) ] — the target-spec of the custom-descriptors instructions
([ref.cast_desc_eq], [br_on_cast_desc_eq], [struct.new_desc]). The target type
is recovered from [d]'s descriptor type, so only the operand is written; a
leading [?] marks a nullable result. The [( )] delimit [d] so it may be any
expression with no precedence clash. *)letdescriptor_operandinstrpp?(nullable=false)d=ifnullablethenpunctuationpp"?";keywordpp"descriptor";punctuationpp"(";boxpp(fun()->instrInstructionppd);punctuationpp")"(* As [branch_ref_instr], for the custom-descriptors [br_on_cast_desc_eq] /
[_fail]: the [[?]descriptor(d)] target-spec precedes the value, so the value
is the sole trailing operand and prints at [Branch] like the plain form. *)letbranch_ref_desc_instrinstrppnamelabelnullableid=boxpp~indent:indent_level(fun()->keywordppname;spacepp();identifierpp"'";identifierpplabel.desc;spacepp();descriptor_operandinstrpp~nullabled;spacepp();instrBranchppi)letcall_instrinstrpp?prefixil=hvboxpp(fun()->(* Keep an optional prefix ([become]/[return]) and the callee glued to the
opening [(]: only the argument list may break. *)hboxpp(fun()->Option.iter(funs->keywordpps;spacepp())prefix;instrCallAndFieldAccessppi);print_arg_list(instrInstruction)ppl)(* All but the last element ([] if empty) — the non-receiver operands of a
stack-switching method call. *)letdrop_lastl=matchList.revlwith[]->[]|_::r->List.revr(* A continuation constructor [T::new(f)] / [T::bind(args…, c)]: the [T::]
namespace constructs a [&T]; the type is always explicit (it is the
namespace itself). *)letcont_construct_instrinstrppctmemberl=boxpp~indent:indent_level(fun()->hboxpp(fun()->identifierppct.desc;operatorpp"::";identifierppmember);print_arg_list(instrInstruction)ppl)letprint_on_clausespphandlers=punctuationpp"[";boxpp(fun()->list_commasep(funppclause->matchclausewith|OnLabel(tag,label)->identifierpptag.desc;spacepp();punctuationpp"->";spacepp();identifierpp"'";identifierpplabel.desc|OnSwitchtag->identifierpptag.desc;spacepp();punctuationpp"->";spacepp();keywordpp"switch")pphandlers);punctuationpp"]"(* A stack-switching method call [recv.meth(args…) on [handlers]]. The receiver
is the LAST operand of [l] — Wasm stack order: it compiles last, exactly as
call_ref's callee does — and prints first, as the method receiver. [args]
renders the parenthesised argument list (closures, so [resume_throw]'s
[tag(payload)] and [switch]'s [tag: t] need no AST form). *)letcont_method_instrinstrppmethl~args~handlers=boxpp~indent:indent_level(fun()->hvboxpp(fun()->hboxpp(fun()->(matchList.revlwith|recv::_->instrCallAndFieldAccesspprecv|[]->operatorpp"_"(* ill-formed operand list: recovery *));punctuationpp".";identifierppmeth);print_arg_list(funppg->gpp)ppargs);matchhandlerswith|[]->()|_::_->spacepp();keywordpp"on";spacepp();print_on_clausespphandlers)letprint_containerpp~opening~closing?(indent=0)opt_typef=hvboxpp~indent(fun()->boxpp(fun()->punctuationppopening;Option.iter(funt->identifierppt.desc;punctuationpp"|")opt_type);f();punctuationppclosing)letstruct_instrppnmf=print_containerpp~opening:"{"~closing:"}"~indent:0nm(fun()->indentppindent_level(fun()->spacepp();f());spacepp())(* As [struct_instr] but with a leading [descriptor(d)] target-spec instead of a
type name (the custom-descriptors [struct.new_desc] / [struct.new_default_desc];
the struct type is recovered from [d]). [print_desc] renders the operand. *)letstruct_desc_instrppprint_descf=hvboxpp~indent:0(fun()->boxpp(fun()->punctuationpp"{";spacepp();print_desc();punctuationpp"|");indentppindent_level(fun()->spacepp();f());spacepp();punctuationpp"}")letarray_instrppnmf=(* Indent the elements one level and break before the closing [\]] at the
array's own column (like [struct_instr]), so a wrapped array reads
[\[t|]/elem,/.../elem,/]\]] with [\]] dedented — not hugging the last
element as [elem,\]]. *)print_containerpp~opening:"["~closing:"]"~indent:0nm(fun()->indentppindent_level(fun()->cutpp();f());cutpp())letrecget_prec(i:_Ast.instr)=matchi.descwith(* Branch-hinting proposal: the hint is a transparent prefix; the wrapped
branch drives precedence, block-ness, and layout. *)|Hinted(_,i)->get_preci|Block_|Loop_|While_|If_|Try_|TryCatch_|TryTable_|If_annotation_|Dispatch_|Match_->Atom|Unreachable|Nop|Hole|Null|Get_|Path_|Char_|String_|Int_|Float_|Struct_|StructDefault_|StructDesc_|StructDefaultDesc_|Array_|ArrayDefault_|ArrayFixed_|ArraySegment_|ArrayGet_|ArraySet_|Sequence_->Atom|Set_|Tee_->Assignement|Call_|TailCall_->CallAndFieldAccess|ContNew_|ContBind_|Suspend_|Switch_->CallAndFieldAccess(* A resume-family instruction with handlers carries its postfix [on] clause,
which binds like [as]/[is]. *)|Resume(_,h,_)|ResumeThrow(_,_,h,_)|ResumeThrowRef(_,h,_)->ifh=[]thenCallAndFieldAccesselseCast|On_->Cast|Cast_|CastDesc_|Test_->Cast|NonNull_->UnaryPostfix|UnOp_->UnaryPrefix|StructGet_|StructSet_|GetDescriptor_->CallAndFieldAccess|BinOp(op,_,_)->letout,_,_=prec_opop.descinout|Let_|Labelled_->Instruction|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Throw_|ThrowRef_|Return_->Branch|Select_->Selectletrecis_block(i:_Ast.instr)=matchi.descwith|Hinted(_,i)->is_blocki|Block_|Loop_|While_|If_|Try_|TryCatch_|TryTable_|If_annotation_|Dispatch_|Match_->true|Call_|Unreachable|Nop|Hole|Null|Get_|Path_|Set_|Tee_|TailCall_|Char_|String_|Int_|Float_|Cast_|CastDesc_|Test_|NonNull_|Struct_|StructDefault_|StructDesc_|StructDefaultDesc_|StructGet_|GetDescriptor_|StructSet_|Array_|ArrayDefault_|ArrayFixed_|ArraySegment_|ArrayGet_|ArraySet_|BinOp_|UnOp_|Let_|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Throw_|ThrowRef_|ContNew_|ContBind_|Suspend_|Resume_|ResumeThrow_|ResumeThrowRef_|Switch_|On_|Return_|Sequence_|Select_|Labelled_->falseletrecstarts_with_block_precprec(i:'aAst.instr)=letactual=get_preciinifprec>actualthenfalseelsematchi.descwith|Hinted(_,i)->starts_with_block_precpreci|Block_|Loop_|While_|If_|Try_|TryCatch_|TryTable_|If_annotation_|Dispatch_|Match_->true|Call(i,_)|ArrayGet(i,_)|ArraySet(i,_,_)->starts_with_block_precCallAndFieldAccessi|Cast(i,_)|CastDesc(i,_,_)|Test(i,_)->starts_with_block_precCasti|NonNulli->starts_with_block_precUnaryPostfixi|UnOp(_,i)->starts_with_block_precUnaryPrefixi|StructGet(i,_)|StructSet(i,_,_)|GetDescriptori->starts_with_block_precCallAndFieldAccessi|BinOp(op,i,_)->let_,left,_=prec_opop.descinstarts_with_block_preclefti|Select(i,_,_)->starts_with_block_precSelecti|On(i,_)->starts_with_block_precCasti(* The method-form stack-switching instructions print their receiver — the
last operand — first. *)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)->(matchList.revlwith|recv::_->starts_with_block_precCallAndFieldAccessrecv|[]->false)|Unreachable|Nop|Hole|Null|Get_|Path_|Set_|Tee_|TailCall_|Char_|String_|Int_|Float_|Struct_|StructDefault_|StructDesc_|StructDefaultDesc_|Array_|ArrayDefault_|ArrayFixed_|ArraySegment_|Let_|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Throw_|ThrowRef_|ContNew_|ContBind_|Suspend_|Return_|Sequence_|Labelled_->falseletstarts_with_blocki=starts_with_block_precInstructioniletarray_element_precedencenmfirsti=ifnm=None&&firstthenmatchi.descwith|BinOp({desc=Or;_},{desc=Get_;_},_)->Atom|_->InstructionelseInstructionletcond_op_string(op:Wax_wasm.Ast.cmp_op)=matchopwith|Eq->"="|Ne->"!="|Lt->"<"|Gt->">"|Le->"<="|Ge->">="letreccond_to_string(c:Wax_wasm.Ast.cond)=matchcwith|Cond_varv->v.desc|Cond_strings->Printf.sprintf"%S"s.desc|Cond_version(a,b,c)->Printf.sprintf"(%d, %d, %d)"abc|Cond_cmp(op,a,b)->Printf.sprintf"%s %s %s"(cond_to_stringa)(cond_op_stringop)(cond_to_stringb)|Cond_andl->Printf.sprintf"all(%s)"(cond_listl)|Cond_orl->Printf.sprintf"any(%s)"(cond_listl)|Cond_notc->Printf.sprintf"not(%s)"(cond_to_stringc)andcond_listl=String.concat", "(List.mapcond_to_stringl)(* The comma-separated case labels of a [br_table]/[dispatch] bracket, ending
in [else <default>]; printed inside a fill box so they pack and wrap. *)letlabel_seqppcasesdefault=List.iter(fun(l:Ast.ident)->identifierpp"'";identifierppl.desc;punctuationpp",";spacepp())cases;boxpp(fun()->keywordpp"else";spacepp();identifierpp"'";identifierppdefault.desc)(* Print [<before>[ <labels> else <default> ]<after>], shared by [br_table] and
[dispatch]. On one line when it fits; otherwise [<before>[] stays on the line,
the labels are filled and indented, and []<after>] is dedented to the box's
column:
<before>[
<labels …>
… else <default>
]<after>
[after] (e.g. the [dispatch] body's [{]) is glued to the []]. *)letbracketed_labelspp~before?(after=fun()->())casesdefault=hvboxpp~indent:0(fun()->hboxpp(fun()->before();punctuationpp"[");indentppindent_level(fun()->spacepp();boxpp(fun()->label_seqppcasesdefault));spacepp();hboxpp(fun()->punctuationpp"]";after()))letmatch_patternpp(pat:Ast.match_pattern)=matchpatwith|MatchCast(bind,rt)->Option.iter(funx->identifierppx.desc;punctuationpp":";spacepp())bind;reftypepprt|MatchNull->keywordpp"null"(*** The instruction printer ***)letrecinstrprecpp(i:_instr)=atomic_nodepp(pp.locatei.info)@@fun()->parenthesesprec(get_preci)pp@@fun()->matchi.descwith|Block{label;typ;block=l}->(* A plain block is always introduced by [do] (a bare or labelled [{ … }]
also parses, but [do] is the canonical form we emit). *)blockpplabel(Some"do")typl|If_annotation{cond;then_body;else_body}->letbranchbody=spacepp();punctuationpp"{";letafter=located_block_contentsppbodyinclose_blockppafterinhvboxpp(fun()->attributepp(Printf.sprintf"#[if(%s)]"(cond_to_stringcond));branchthen_body;Option.iter(funb->newlinepp();attributepp"#[else]";branchb)else_body)|Loop{label;typ;block=l}->blockpplabel(Some"loop")typl|While{label;cond;step;block=l}->hvboxpp(fun()->boxpp(fun()->block_labelpplabel;keywordpp"while";indentppindent_level(fun()->spacepp();instrInstructionppcond;(* Zig-style continue-expression: [: (step)] after the cond. *)matchstepwith|None->()|Somes->spacepp();punctuationpp":";spacepp();punctuationpp"(";instrInstructionpps;punctuationpp")");spacepp();punctuationpp"{");letafter=located_block_contentspplinclose_blockppafter)|If{label;typ;cond;if_block;else_block}->hvboxpp(fun()->boxpp(fun()->block_labelpplabel;keywordpp"if";indentppindent_level(fun()->spacepp();instrInstructionppcond;ifneed_blocktypetypthen(spacepp();boxpp~indent:indent_level(fun()->punctuationpp"=>";spacepp();blocktypepptyp)));spacepp();punctuationpp"{");letif_after=located_block_contentsppif_blockinmatchelse_blockwith|Someelse_block->hvboxpp(fun()->boxpp(fun()->punctuationpp"}";print_triviappif_after;spacepp();keywordpp"else";spacepp();punctuationpp"{");letelse_after=located_block_contentsppelse_blockinclose_blockppelse_after)|None->close_blockppif_after)|TryCatch{label;typ;block=l;arms}->hvboxpp(fun()->boxpp(fun()->block_labelpplabel;keywordpp"try";spacepp();ifneed_blocktypetypthen(blocktypepptyp;spacepp());punctuationpp"{");letblock_after=located_block_contentspplinhvboxpp(fun()->boxpp(fun()->punctuationpp"}";print_triviappblock_after;spacepp();keywordpp"catch";spacepp();punctuationpp"{");indentppindent_level(fun()->List.iter(funarm->spacepp();hvboxpp(fun()->boxpp(fun()->(matcharm.arm_tagwith|Sometag->identifierpptag.desc|None->operatorpp"_");spacepp();ifarm.arm_refthen(operatorpp"&";spacepp());punctuationpp"=>";spacepp();punctuationpp"{");letafter=located_block_contentspparm.arm_bodyinclose_blockppafter))arms);(* The break before the closing [}] must sit outside the [indent]
above so it lands at the catch block's own column, not the
arms' deeper indent. *)spacepp();punctuationpp"}"))|Try{label;typ;block=l;catches;catch_all}->hvboxpp(fun()->boxpp(fun()->block_labelpplabel;keywordpp"try_legacy";spacepp();ifneed_blocktypetypthen(blocktypepptyp;spacepp());punctuationpp"{");letblock_after=located_block_contentspplinhvboxpp(fun()->boxpp(fun()->punctuationpp"}";print_triviappblock_after;spacepp();keywordpp"catch";spacepp();punctuationpp"{");indentppindent_level(fun()->List.iter(fun(tag,block)->spacepp();hvboxpp(fun()->boxpp(fun()->identifierpptag.desc;spacepp();punctuationpp"=>";spacepp();punctuationpp"{");letafter=located_block_contentsppblockinclose_blockppafter))catches;Option.iter(funblock->spacepp();hvboxpp(fun()->boxpp(fun()->operatorpp"_";spacepp();punctuationpp"=>";spacepp();punctuationpp"{");letafter=located_block_contentsppblockinclose_blockppafter))catch_all);(* The break before the closing [}] must sit outside the [indent]
above so it lands at the catch block's own column, not the
handlers' deeper indent. *)spacepp();punctuationpp"}"))|TryTable{label;typ=bt;block=l;catches}->hvboxpp(fun()->boxpp(fun()->block_labelpplabel;keywordpp"try";spacepp();ifneed_blocktypebtthen(blocktypeppbt;spacepp());punctuationpp"{");letblock_after=located_block_contentspplinhvboxpp(fun()->boxpp(fun()->punctuationpp"}";print_triviappblock_after;spacepp();keywordpp"catch";spacepp();punctuationpp"[");indentppindent_level(fun()->letlast=List.lengthcatches-1inList.iteri(funicatch->spacepp();boxpp(fun()->matchcatchwith|Catch(tag,label)->identifierpptag.desc;spacepp();punctuationpp"->";spacepp();identifierpp"'";identifierpplabel.desc;ifi<lastthenpunctuationpp","|CatchRef(tag,label)->identifierpptag.desc;spacepp();operatorpp"&";spacepp();punctuationpp"->";spacepp();identifierpp"'";identifierpplabel.desc;ifi<lastthenpunctuationpp","|CatchAlllabel->operatorpp"_";spacepp();punctuationpp"->";spacepp();identifierpp"'";identifierpplabel.desc;ifi<lastthenpunctuationpp","|CatchAllReflabel->operatorpp"_";spacepp();operatorpp"&";spacepp();punctuationpp"->";spacepp();identifierpp"'";identifierpplabel.desc;ifi<lastthenpunctuationpp","))catches);(* Break before the closing [\]] so a wrapped handler list dedents
it to the [try]'s column rather than hugging the last handler. *)cutpp();punctuationpp"]"))|Unreachable->keywordpp"unreachable"|Nop->operatorpp"nop"|Hole->operatorpp"_"|Getx->identifierppx.desc|Path(x,y)->identifierppx.desc;operatorpp"::";identifierppy.desc|Set(x,op,i)->boxpp~indent:indent_level(fun()->identifierppx.desc;spacepp();(* [x op= e] for a compound assignment; a plain [=] otherwise. *)operatorpp(matchopwithNone->"="|Someo->binopo.desc^"=");spacepp();instrInstructionppi)|Tee(x,i)->boxpp~indent:indent_level(fun()->identifierppx.desc;spacepp();operatorpp":=";spacepp();instrInstructionppi)|Call(i,l)->call_instrinstrppil|TailCall(i,l)->call_instrinstrpp~prefix:"become"il|Labelled(l,i)->print_key_valueppl.desc(instrInstruction)i|Charc->letn=Uchar.utf_8_byte_lengthcinletb=Bytes.createninignore(Bytes.set_utf_8_ucharb0c);letlen,s=Wax_utils.Unicode.escape_string~hex_prefix:"x"(Bytes.to_stringb)instringpp"\'";stringpp~len:(Somelen)s;stringpp"\'"|String(t,s)->Option.iter(funt->type_ppt.desc;operatorpp"#")t;letlen,s=Wax_utils.Unicode.escape_string~hex_prefix:"x"sinstringpp"\"";stringpp~len:(Somelen)s;stringpp"\""|Ints|Floats->constantpps|Cast(i,t)->boxpp~indent:indent_level(fun()->instrCastppi;spacepp();boxpp(fun()->keywordpp"as";spacepp();casttypeppt))|CastDesc(i,nullable,d)->boxpp~indent:indent_level(fun()->instrCastppi;spacepp();boxpp(fun()->keywordpp"as";spacepp();descriptor_operandinstrpp~nullabled))|NonNulli->instrUnaryPostfixppi;operatorpp"!"|Test(i,t)->boxpp~indent:indent_level(fun()->instrCastppi;spacepp();boxpp(fun()->keywordpp"is";spacepp();reftypeppt))|Struct(nm,l)->struct_instrppnm(fun()->list_commasep_trailingstruct_field_kvppl)|StructDefaultnm->struct_instrppnm(fun()->punctuationpp"..")|StructDesc(d,l)->struct_desc_instrpp(fun()->descriptor_operandinstrppd)(fun()->list_commasep_trailingstruct_field_kvppl)|StructDefaultDescd->struct_desc_instrpp(fun()->descriptor_operandinstrppd)(fun()->punctuationpp"..")|StructGet(i,s)->field_receiverppi;operatorpp".";identifierpps.desc|GetDescriptori->field_receiverppi;operatorpp".";keywordpp"descriptor"|StructSet(i,s,i')->boxpp~indent:indent_level(fun()->field_receiverppi;operatorpp".";identifierpps.desc;spacepp();operatorpp"=";spacepp();instrInstructionppi')|Array(nm,i,n)->array_instrppnm(fun()->instr(array_element_precedencenmtruei)ppi;punctuationpp";";spacepp();instrInstructionppn)|ArrayDefault(nm,n)->array_instrppnm(fun()->punctuationpp"..;";spacepp();instrInstructionppn)|ArrayFixed(nm,l)->array_instrppnm(fun()->list_commasep_trailing(functx(first,i)->instr(array_element_precedencenmfirsti)ctxi)pp(List.mapi(funni->(n=0,i))l))|ArraySegment(nm,d,off,len)->hvboxpp~indent:0(fun()->boxpp(fun()->punctuationpp"[";Option.iter(funt->identifierppt.desc;punctuationpp"|")nm;spacepp();identifierppd.desc;spacepp();operatorpp"@");indentppindent_level(fun()->spacepp();instrInstructionppoff);punctuationpp";";indentppindent_level(fun()->spacepp();instrInstructionpplen);cutpp();punctuationpp"]")|ArrayGet(i1,i2)->boxpp~indent:indent_level(fun()->instrCallAndFieldAccessppi1;cutpp();boxpp(fun()->operatorpp"[";instrInstructionppi2;operatorpp"]"))|ArraySet(i1,i2,i3)->boxpp~indent:indent_level(fun()->instrCallAndFieldAccessppi1;cutpp();boxpp(fun()->operatorpp"[";instrInstructionppi2;operatorpp"]");spacepp();operatorpp"=";spacepp();instrInstructionppi3)|BinOp(op,i,i')->let_,left,right=prec_opop.descin(* The [precedence] lint (see [Typing.lint_precedence]) flags a shift mixed
with arithmetic, or a comparison with a bitwise operator, written
without parentheses — precedence alone would not require them. Emit them
anyway around such an operand (by demanding an [Atom] there) so
re-printed / decompiled Wax stays quiet under the lint. The confusion
table is shared with the lint ({!Ast_utils.confusing_precedence}). *)letoperand_precdefault(child:_instr)=matchchild.descwith|BinOp(child_op,_,_)whenAst_utils.confusing_precedence(Ast_utils.binop_kindop.desc)(Ast_utils.binop_kindchild_op.desc)->Atom|_->defaultinboxpp~indent:indent_level(fun()->instr(operand_preclefti)ppi;(* Break *before* the operator (rustfmt style: a wrapped operator
leads its continuation line), so only the space ahead of it may
break; the space after it is a plain, non-breaking blank. The
operator carries its own location, so a comment between the left
operand and it attaches to the operand, ahead of the break. *)spacepp();atomic_nodepp(Someop.info)(fun()->operatorpp(binopop.desc));Wax_utils.Printer.stringpp.base.printer" ";instr(operand_precrighti')ppi')|UnOp(op,i)->atomic_nodepp(Someop.info)(fun()->operatorpp(unopop.desc));instrUnaryPrefixppi|Let([(None,typ)],Somei)->(* An anonymous binding is a discarded value: printed [_ = e] (or, when a
width annotation is load-bearing, [_: t = e]) — without [let], since
nothing is bound. Mirrors the [Set] layout. *)boxpp~indent:indent_level(fun()->hboxpp(fun()->operatorpp"_";Option.iter(funt->punctuationpp":";spacepp();valtypeppt)typ;spacepp();operatorpp"=");spacepp();instrInstructionppi)|Let(l,i)->boxpp~indent:indent_level(fun()->(* Keep [let pat =] together as one unit: when the value breaks the
line after [=], without this box the outer box would also split
[let]/[pat]/[=] across lines. *)hboxpp(fun()->keywordpp"let";spacepp();(matchlwith|[p]->print_typed_patppp|l->print_paren_listprint_typed_patppl);ifOption.is_someithen(spacepp();keywordpp"="));Option.iter(funi->spacepp();instrInstructionppi)i)|Br(label,i)->branch_instrinstrpp"br"labeli|Br_if(label,i)->branch_instrinstrpp"br_if"label(Somei)|Br_on_null(label,i)->branch_instrinstrpp"br_on_null"label(Somei)|Br_on_non_null(label,i)->branch_instrinstrpp"br_on_non_null"label(Somei)|Br_on_cast(label,ty,i)->branch_ref_instrinstrpp"br_on_cast"labeltyi|Br_on_cast_fail(label,ty,i)->branch_ref_instrinstrpp"br_on_cast_fail"labeltyi|Br_on_cast_desc_eq(label,nullable,i,d)->branch_ref_desc_instrinstrpp"br_on_cast"labelnullableid|Br_on_cast_desc_eq_fail(label,nullable,i,d)->branch_ref_desc_instrinstrpp"br_on_cast_fail"labelnullableid(* Branch-hinting proposal: [#[likely]] / [#[unlikely]] prefixing the wrapped
conditional branch. The attribute is a bare prefix so the branch keeps its own
layout (and stays on the same line: [#[likely] if …]). *)|Hinted(h,inner)->branch_hint_attrpph;instrprecppinner|Br_table(labels,i)->letdefault,cases=matchList.revlabelswith|default::rev_cases->(default,List.revrev_cases)|[]->assertfalseinboxpp~indent:indent_level(fun()->bracketed_labelspp~before:(fun()->keywordpp"br_table";spacepp())casesdefault;spacepp();instrBranchppi)|Dispatch{index;cases;default;arms}->hvboxpp(fun()->(* Head: [dispatch <index> [ <labels> else <default> ] {], laid out on
one line or, when too wide, as
dispatch <index> [
<labels, filled and indented>
] {
with the [']'] dedented to the [dispatch] column (see
[bracketed_labels]). *)bracketed_labelspp~before:(fun()->keywordpp"dispatch";spacepp();(* Parenthesise a non-atomic index: the following '[' would
otherwise bind to the index's last atom as an array access. *)instrAtomppindex;spacepp())~after:(fun()->spacepp();punctuationpp"{")casesdefault;ifarms<>[]then(indentppindent_level(fun()->List.iter(fun(l,body)->newlinepp();blockpp(Somel)None{params=[||];results=[||]}body)arms);newlinepp());punctuationpp"}")|Match{scrutinee;arms;default}->letarmpat_printerbody=newlinepp();hvboxpp(fun()->boxpp(fun()->pat_printer();spacepp();punctuationpp"=>";spacepp();punctuationpp"{");letafter=located_block_contentsppbodyinclose_blockppafter)inhvboxpp(fun()->boxpp(fun()->keywordpp"match";spacepp();(* Parenthesise a non-atomic scrutinee: the following '{' would
otherwise read as a struct/block continuation. *)instrAtomppscrutinee;spacepp();punctuationpp"{");indentppindent_level(fun()->List.iter(fun(pat,body)->arm(fun()->match_patternpppat)body)arms;(* The default arm is compulsory, so always print it. *)arm(fun()->operatorpp"_")default);newlinepp();punctuationpp"}")|Returni->boxpp~indent:indent_level(fun()->keywordpp"return";Option.iter(funi->spacepp();instrBranchppi)i)|Throw(tag,args)->(* Call-like: [throw tag(x, y)]. *)hvboxpp(fun()->hboxpp(fun()->keywordpp"throw";spacepp();identifierpptag.desc);print_arg_list(instrInstruction)ppargs)|ThrowRefi->boxpp~indent:indent_level(fun()->keywordpp"throw_ref";spacepp();instrBranchppi)|ContNew(ct,i)->cont_construct_instrinstrppct"new"[i](* The source continuation type is inferred from the last operand's static
type, so only the destination — the namespace — is written. *)|ContBind(_,dst,l)->cont_construct_instrinstrppdst"bind"l|Suspend(tag,l)->boxpp~indent:indent_level(fun()->keywordpp"suspend";spacepp();identifierpptag.desc;cutpp();print_paren_list(instrInstruction)ppl)(* The type immediate of the resume family and [switch] is inferred from the
receiver's static type, so it is not written. *)|Resume(_,handlers,l)->cont_method_instrinstrpp"resume"l~args:(List.map(funapp->instrInstructionppa)(drop_lastl))~handlers|ResumeThrow(_,tag,handlers,l)->(* The tag is invoked with its payload, [c.resume_throw(exc(p))], exactly
as [throw exc(p)] spells it. *)cont_method_instrinstrpp"resume_throw"l~args:[(funpp->boxpp~indent:indent_level(fun()->identifierpptag.desc;print_arg_list(instrInstruction)pp(drop_lastl)));]~handlers|ResumeThrowRef(_,handlers,l)->cont_method_instrinstrpp"resume_throw_ref"l~args:(List.map(funapp->instrInstructionppa)(drop_lastl))~handlers|Switch(_,tag,l)->cont_method_instrinstrpp"switch"l~args:(List.map(funapp->instrInstructionppa)(drop_lastl)@[(funpp->print_key_valuepp"tag"(funpp(t:ident)->identifierppt.desc)tag);])~handlers:[]|On(i,handlers)->boxpp~indent:indent_level(fun()->instrCastppi;spacepp();keywordpp"on";spacepp();print_on_clausespphandlers)|Sequencel->print_paren_list(instrInstruction)ppl|Select(i1,i2,i3)->boxpp~indent:indent_level(fun()->instrComparisonppi1;cutpp();operatorpp"?";instrAssignementppi2;cutpp();operatorpp":";instrAssignementppi3)|Null->keywordpp"null"(* A struct-literal field. A punned field ([None], written [{x}]) prints as the
bare name; an explicit field prints as [name: value]. *)andstruct_field_kvpp(nm,i)=matchiwith|None->identifierppnm.desc|Somei->print_key_valueppnm.desc(instrInstruction)iandfield_receiverppi=(* A bare numeric literal receiver would be misparsed: [0.foo] lexes [0.]
as a float, so parenthesize it. *)matchi.descwith|Int_|Float_->punctuationpp"(";boxpp(fun()->instrInstructionppi;punctuationpp")")|_->instrCallAndFieldAccessppiandblockpplabelkindbt(l:(_instrlist,location)annotated)=hvboxpp(fun()->boxpp(fun()->block_labelpplabel;Option.iter(funkind->keywordppkind;spacepp())kind;ifneed_blocktypebtthen(blocktypeppbt;spacepp());punctuationpp"{");letafter=located_block_contentspplinclose_blockppafter)anddeliminated_instrpp(i:_instr)=ifis_blockitheninstrInstructionppielse(instr(ifstarts_with_blockithenAtomelseInstruction)ppi;(* Hold any deferred trailing comment so the [;] prints on the statement's
line, ahead of the comment ([expr; // c] rather than [expr // c] then a
lone [;] on the next line). *)Wax_utils.Printer.with_held_eolpp.base.printer(fun()->punctuationpp";"))andblock_contentspp(l:_instrlist)=(* A non-empty block always breaks across lines (rustfmt never keeps a block
body on one line), so every separator here is a hard [newline]; the
enclosing box then lays the body out vertically. An empty block stays
[{}]. *)ifl<>[]then(indentppindent_level(fun()->List.iter(funi->newlinepp();deliminated_instrppi)l);newlinepp())(* Print the contents of a brace-delimited block, looking the block's own
location up so a comment opening the clause attaches here rather than to the
condition, and so an own-line comment trailing the last statement ([within]
the block's span) renders *inside* the block at the statement indentation.
[before] precedes the body; [within] closes it. The block's [after] (comments
past the closing [}]) is *returned*, not printed, so the caller can emit it on
the far side of the [}] — mirroring the WAT printer, where [within] prints
before the closing [)] and [after] after it. *)andlocated_block_contentspp(b:(_instrlist,location)annotated)=letassoc=get_triviapp(Someb.info)inprint_triviappassoc.before;ifb.desc<>[]||assoc.within<>[]then(indentppindent_level(fun()->List.iter(funi->newlinepp();deliminated_instrppi)b.desc;print_triviappassoc.within);newlinepp());assoc.after(* Emit a block's closing [}] followed by the trailing comments [located_block_contents]
returned, so a comment anchored past the [}] renders after it (and, before an
[else]/[catch] continuation, back between the [}] and that keyword — where it
was written). *)andclose_blockppafter=punctuationpp"}";print_triviappafter(*** Declarations, attributes, and module fields ***)letfundecl?(exact=false)~tagpp(name,typ,sign)=(* The whole signature is one all-or-nothing group anchored at the [fn]
column: [fn name] stays glued (its own [hbox]) and so does [-> Ret], so the
only thing that can break — when [fn name(params) -> Ret] overflows — is
the parameter list, which then lays out one parameter per line. *)hvboxpp(fun()->hboxpp(fun()->keywordpp(iftagthen"tag"else"fn");spacepp();identifierppname.desc;(* An exact declaration with an inline signature marks the name
([fn f!(…)]); with a named type the marker hugs the type
([fn f: !t]). *)ifexact&&Option.is_nonetypthenpunctuationpp"!";Option.iter(funtyp->punctuationpp":";spacepp();ifexactthenpunctuationpp"!";identifierpptyp.desc;spacepp())typ);Option.iter(funty->raw_functypeppty)sign)letprint_attribute_genopen_pp(name,i,guard)=boxpp~indent:indent_level(fun()->attributeppopen_;attributeppname;(matchiwith|None->()|Somei->spacepp();attributepp"=";spacepp();with_styleppAttribute(fun()->instrInstructionppi));(* A per-attribute guard, [#[export = "n", if(<cond>)]]. *)(matchguardwith|None->()|Somec->attributepp",";spacepp();attributepp(Printf.sprintf"if(%s)"(cond_to_stringc.desc)));attributepp"]")letprint_attributeppa=print_attribute_gen"#["ppa(* Module-level inner attribute: [#![module = "name"]]. *)letprint_inner_attributeppa=print_attribute_gen"#!["ppa(* Separate attributes at this (enclosing) level rather than with a trailing
space inside each attribute's box: a break between them then lands at the
enclosing box's indentation, so stacked attributes stay aligned instead of
each indenting relative to the previous one's box. *)letprint_attributesppattributes=List.iteri(funia->ifi>0thenspacepp();print_attributeppa)attributesletprint_attr_prefixppattributes_listcontent_fn=hvboxpp(fun()->ifattributes_list<>[]then(print_attributesppattributes_list;newlinepp());content_fn())letprint_data_bytespps=letlen,s=Wax_utils.Unicode.escape_string~hex_prefix:"x"sinstringpp"\"";stringpp~len:(Somelen)s;stringpp"\""letvec_shape_name:Wax_utils.V128.shape->string=function|I8x16->"i8x16"|I16x8->"i16x8"|I32x4->"i32x4"|I64x2->"i64x2"|F32x4->"f32x4"|F64x2->"f64x2"(* A data numeric run [[head: e1, e2, …]]: kept in one indented box so it lays
out inline when it fits and, when it overflows, breaks after the [:] with its
values indented under the [[] rather than splitting the run head. Laid out
exactly like an array literal (see [array_instr]): inline when it fits;
otherwise one value per line with a trailing comma and the [\]] dedented on
its own line — differing only in the [type:] head (vs an array's [type|]). *)letprint_runppheadprint_elemelems=hvboxpp~indent:0(fun()->boxpp(fun()->punctuationpp"[";type_pphead;punctuationpp":");indentppindent_level(fun()->spacepp();list_commasep_trailingprint_elemppelems);cutpp();punctuationpp"]")(* One lane group of a [v128] run: [i32x4(1, 2, 3, 4)]. *)letprint_v128_grouppp(v:(Wax_utils.V128.t,_)Ast.annotated)=type_pp(vec_shape_namev.desc.shape);punctuationpp"(";boxpp(fun()->list_commasep(funppc->constantppc)ppv.desc.components);punctuationpp")"(* A data segment's contents: constant elements (string literals, numeric runs
[[i16: …]], [v128] runs) concatenated with [++]. Only called for a
non-empty segment; an empty one omits the [= …] entirely (see callers). *)letprint_data_elempp=function|Ast.Data_strings->print_data_bytespps|Ast.Data_run(st,values)->print_runpp(suffix_stringst)(funpp(v:(string,_)Ast.annotated)->constantppv.desc)values|Ast.Data_v128vs->print_runpp"v128"print_v128_groupvsletprint_data_initppinit=boxpp~indent:indent_level(fun()->matchinitwith|[]->()|first::rest->print_data_elemppfirst;List.iter(fune->(* Fold like a binary operator (see [BinOp]): break *before*
[++], which then leads its continuation line; the space after
it never breaks. *)spacepp();operatorpp"++";Wax_utils.Printer.stringpp.base.printer" ";print_data_elemppe)rest)letprint_data_nameppn=matchnwith|Some(n:ident)->identifierppn.desc|None->punctuationpp"_"letsimple_inline_instr(i:_Ast.instr)=matchi.descwith|Get_|Path_|Char_|String_|Int_|Float_->true|_->falseletprint_square_instr?(leading_space=false)ppi=ifsimple_inline_instrithen(ifleading_spacethenspacepp();punctuationpp"[";instrInstructionppi;punctuationpp"]")else(ifleading_spacethenhboxpp(fun()->spacepp();punctuationpp"[")elsepunctuationpp"[";indentppindent_level(fun()->newlinepp();instrInstructionppi);newlinepp();punctuationpp"]")letprint_square_instr_list?(multiline=false)ppl=ifnotmultilinethen(punctuationpp"[";boxpp(fun()->list_commasep(funppi->instrInstructionppi)ppl);punctuationpp"]")else(punctuationpp"[";indentppindent_level(fun()->newlinepp();list_commasep(funppi->instrInstructionppi)ppl);newlinepp();punctuationpp"]")letprint_offset_bracketsppoff=print_square_instr~leading_space:trueppoffletprint_targeted_offsetpptargetoff=hboxpp(fun()->spacepp();operatorpp"@";spacepp();identifierpptarget.desc);print_offset_bracketsppoffletprint_eq_square_instr_listppl=hboxpp(fun()->spacepp();punctuationpp"=";spacepp());print_square_instr_list~multiline:(List.lengthl>3)pplletprint_memdatapp(d:_Ast.memdata)=boxpp~indent:indent_level(fun()->hboxpp(fun()->keywordpp"data";spacepp();print_data_nameppd.data_name;spacepp();operatorpp"@");print_offset_bracketsppd.offset;ifd.init<>[]then(hboxpp(fun()->spacepp();punctuationpp"=");spacepp();print_data_initppd.init);punctuationpp";")letprint_limitspplimits=Option.iter(fun(mi,ma)->spacepp();punctuationpp"[";constantpp(Wax_utils.Uint64.to_stringmi);Option.iter(funm->punctuationpp",";spacepp();constantpp(Wax_utils.Uint64.to_stringm))ma;punctuationpp"]")limits(* Print one entry of an [import "module" { ... }] block: its attributes and an
[#[import = "name"]] override (when it is imported under a name other than
its own), then the declaration itself. *)letprint_import_declpp(decl:Ast.import_decl)=print_attr_prefixppdecl.attributes(fun()->boxpp(fun()->(matchdecl.kindwith|Import_func{typ;sign;exact}->fundecl~exact~tag:falsepp(decl.id,typ,sign)|Import_tag{typ;sign}->fundecl~tag:truepp(decl.id,typ,sign)|Import_global{mut;typ}->keywordpp(ifmutthen"let"else"const");spacepp();identifierppdecl.id.desc;punctuationpp":";spacepp();valtypepptyp|Import_memory{address_type;limits;page_size_log2;shared}->keywordpp"memory";spacepp();identifierppdecl.id.desc;punctuationpp":";spacepp();keywordpp(matchaddress_typewith`I32->"i32"|`I64->"i64");print_limitspplimits;Option.iter(funp->spacepp();keywordpp"pagesize";spacepp();constantpp(Int64.to_string(Int64.shift_left1Lp)))page_size_log2;ifsharedthen(spacepp();keywordpp"shared")|Import_table{address_type;reftype=rt;limits}->keywordpp"table";spacepp();identifierppdecl.id.desc;punctuationpp":";spacepp();(matchaddress_typewith|`I32->()|`I64->keywordpp"i64";spacepp());reftypepprt;print_limitspplimits);semicolonpp))letrecmodulefieldppfield=atomic_nodepp(Somefield.info)@@fun()->matchfield.descwith|Typet->rectypeppt|Module_annotationattrs->hvboxpp(fun()->List.iteri(funia->ifi>0thenspacepp();print_inner_attributeppa)attrs)|Func{name;typ;sign;body=label,body;attributes=a}->print_attr_prefixppa(fun()->hvboxpp(fun()->boxpp(fun()->fundecl~tag:falsepp(name,typ,sign);(* Glue the opening [{] to the signature so [fundecl] counts
it when deciding whether to break the parameter list;
otherwise a signature one or two columns too long leaves
[{] stranded on its own line instead. *)hboxpp(fun()->spacepp();block_labelpplabel;punctuationpp"{"));block_contentsppbody;punctuationpp"}"))|Global{name;mut;typ;def;attributes=a}->print_attr_prefixppa(fun()->boxpp~indent:indent_level(fun()->keywordpp(ifmutthen"let"else"const");spacepp();identifierppname.desc;Option.iter(funt->punctuationpp":";spacepp();valtypeppt)typ;spacepp();punctuationpp"=";spacepp();instrInstructionppdef;semicolonpp))|Tag{name;typ;sign;attributes=a}->print_attr_prefixppa(fun()->boxpp(fun()->fundecl~tag:truepp(name,typ,sign);semicolonpp))|Memory{name;address_type;limits;page_size_log2;shared;data;attributes=a;}->print_attr_prefixppa(fun()->hvboxpp(fun()->boxpp(fun()->keywordpp"memory";spacepp();identifierppname.desc;punctuationpp":";spacepp();keywordpp(matchaddress_typewith`I32->"i32"|`I64->"i64");Option.iter(fun(mi,ma)->spacepp();punctuationpp"[";constantpp(Wax_utils.Uint64.to_stringmi);Option.iter(funm->punctuationpp",";spacepp();constantpp(Wax_utils.Uint64.to_stringm))ma;punctuationpp"]")limits;Option.iter(funp->spacepp();keywordpp"pagesize";spacepp();constantpp(Int64.to_string(Int64.shift_left1Lp)))page_size_log2;ifsharedthen(spacepp();keywordpp"shared"));matchdatawith|[]->semicolonpp|_->hboxpp(fun()->spacepp();punctuationpp"{");indentppindent_level(fun()->List.iter(fund->spacepp();print_memdatappd)data);spacepp();punctuationpp"}"))|Data{name;mode;init;attributes=a}->print_attr_prefixppa(fun()->boxpp~indent:indent_level(fun()->hboxpp(fun()->keywordpp"data";spacepp();print_data_nameppname);(matchmodewith|Passive->()|Active(mem,off)->print_targeted_offsetppmemoff);ifinit<>[]then(hboxpp(fun()->spacepp();punctuationpp"=");spacepp();print_data_initppinit);semicolonpp))|Table{name;address_type;reftype=rt;limits;init;attributes=a}->print_attr_prefixppa(fun()->boxpp~indent:indent_level(fun()->hboxpp(fun()->keywordpp"table";spacepp();identifierppname.desc;punctuationpp":";spacepp();(matchaddress_typewith|`I32->()|`I64->keywordpp"i64";spacepp());reftypepprt;Option.iter(fun(mi,ma)->spacepp();punctuationpp"[";constantpp(Wax_utils.Uint64.to_stringmi);Option.iter(funm->punctuationpp",";spacepp();constantpp(Wax_utils.Uint64.to_stringm))ma;punctuationpp"]")limits);Option.iter(fune->hboxpp(fun()->spacepp();punctuationpp"=");spacepp();instrInstructionppe)init;semicolonpp))|Elem{name;reftype=rt;mode;init;attributes=a}->print_attr_prefixppa(fun()->boxpp~indent:indent_level(fun()->hboxpp(fun()->keywordpp"elem";spacepp();identifierppname.desc;punctuationpp":";spacepp();reftypepprt);(matchmodewith|EPassive->()|EActive(tab,off)->print_targeted_offsetpptaboff);print_eq_square_instr_listppinit;semicolonpp))|Import{module_;decl}->boxpp(fun()->keywordpp"import";spacepp();print_data_bytesppmodule_.desc;spacepp();print_import_declppdecl.desc)|Import_group{module_;decls}->hvboxpp(fun()->boxpp(fun()->keywordpp"import";spacepp();print_data_bytesppmodule_.desc;spacepp();punctuationpp"{");(* Like any brace-delimited block (see [block_contents]), a non-empty
import group always lays its declarations out vertically, one per
line; an empty group stays [{}]. *)ifdecls<>[]then(indentppindent_level(fun()->List.iter(fund->newlinepp();print_import_declppd.desc)decls);newlinepp());punctuationpp"}")|Conditional{cond;then_fields;else_fields}->(* Braces are mandatory; a branch is a located field list. Like a block
body (see [located_block_contents]), a non-empty branch always breaks
across lines — hard [newline]s, so the [{] and each field land on their
own line even for a short branch — and an own-line comment trailing its
last field renders inside it. *)letbranchb=spacepp();punctuationpp"{";letassoc=get_triviapp(Someb.info)inprint_triviappassoc.before;ifb.desc<>[]||assoc.within<>[]then(indentppindent_level(fun()->List.iter(funf->newlinepp();modulefieldppf)b.desc;print_triviappassoc.within);newlinepp());close_blockppassoc.afterinhvboxpp(fun()->attributepp(Printf.sprintf"#[if(%s)]"(cond_to_stringcond));branchthen_fields;Option.iter(fune->newlinepp();attributepp"#[else]";branche)else_fields)(*** Entry points ***)letmodule_?(color=Auto)?out_channel?(tail=[])?collectprinter~trivia(l:locationmodule_)=(* [collect] marks the dry trivia-collection traversal; time the real emit
only, so a single "output" timing is reported. *)Wax_utils.Debug.timed_if(collect=None)"output"@@fun()->letuse_color=should_use_color~color~out_channelinlettheme=get_themeuse_colorinletpp={base=Wax_utils.Styled_printer.create~printer~theme?collect~trivia();locate=(funl->Somel);}inhvboxpp(fun()->list~sep:spacemodulefieldppl);(* Trailing comments owned by no node. Drop trailing blank lines so the file
does not end with spurious blank lines. *)lettail=Wax_utils.Trivia.drop_trailing_blank_linestailinprint_triviapptail(* Context for printing AST fragments in diagnostics: no trivia, no location
lookup, colour decided from [stderr]. *)letdiagnostic_ctxprinter=letuse_color=should_use_color~color:Auto~out_channel:(Somestderr)inlettheme=get_themeuse_colorin{base=Wax_utils.Styled_printer.create~printer~theme~trivia:(Hashtbl.create0)();locate=(fun_->None);}(* Render an AST fragment into a caller-supplied styled printer — used to embed a
type in a diagnostic [Message], so it shares the message's colour theme and
width (unlike [diagnostic_ctx], which sniffs [stderr]). No trivia, no location
lookup. Defined before the [Printer.t]-taking entries below shadow [valtype]
/ [comptype] with the diagnostic-context wrappers. *)letstyled_ctxbase={base;locate=(fun_->None)}letvaltype_styledbasei=valtype(styled_ctxbase)iletcomptype_styledbasei=comptype(styled_ctxbase)iletinstrprinteri=instrInstruction(diagnostic_ctxprinter)iletvaltypeprinteri=valtype(diagnostic_ctxprinter)iletcomptypeprinteri=comptype(diagnostic_ctxprinter)iletstoragetypeprinteri=storagetype(diagnostic_ctxprinter)iletfieldtypeprinteri=fieldtype(diagnostic_ctxprinter)iletsubtypeprinterfield=subtype(diagnostic_ctxprinter)field