123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196(*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.
Every Wax render goes through {!run_string} / {!run_channel} below, which
apply it, rather than passing it as the printer's [?width] by hand. *)letwidth=100(* The printer runners with that width applied — [Printer.run_string] /
[Printer.run_channel] otherwise default to 78, silently reflowing Wax output
to the WebAssembly-text width. *)letrun_stringf=Wax_utils.Printer.run_string~widthfletrun_channelocf=Wax_utils.Printer.run_channel~widthocf(*** 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()(* Compilation-hints proposal: the frequency attribute. The reserved values print
as their own keywords; otherwise the byte stands for an executions-per-call
ratio. [%.17g] for a fractional one, so the decimal reads back as the same
double and re-encodes to the same byte.
A byte a hand-written binary put outside the formula's range has no ratio and
so no Wax spelling — Wax has no raw-byte form, unlike the WAT annotation — so
decompiling one to Wax drops the hint. It cannot arise from Wax itself, and a
dropped hint costs performance, never meaning. *)letfreq_attrpp(b:Wax_wasm.Hints.freq)=letemits=attributepp("#["^s^"]");spacepp()inifb=Wax_wasm.Hints.never_optthenemit"never_opt"elseifb=Wax_wasm.Hints.always_optthenemit"always_opt"elsematchWax_wasm.Hints.ratio_of_freqbwith|SomerwhenFloat.is_integerr&&r>=1.->emit(Printf.sprintf"freq = %.0f"r)|Somer->emit(Printf.sprintf"freq = %.17g"r)|None->()(* Compilation-hints proposal: the call-target attribute. Each frequency is a whole
percent on the wire and prints back as the fraction of 1 it was written as, so
the round trip is exact ([%g] suffices: a percent never needs more digits). A
target's name is printed as the plain identifier it is, not styled as one, since
it is part of the attribute rather than a reference the reader can navigate. *)lettargets_attrppl=attributepp"#[targets(";List.iteri(funn(f,pct)->ifn>0thenattributepp", ";attributepp(Printf.sprintf"%s: %g"f.Ast.Annot.desc(float_of_intpct/.100.)))l;attributepp")]";spacepp()(* Branch-hinting / compilation-hints proposals: the attributes an instruction's
hints print as, before the instruction itself, in section order.
Every hint is printed, on a legal target or not: on an illegal one, printing the
attribute is what makes the Wax parser reject the module, so an invalid binary
stays invalid through the round trip instead of being laundered into valid
source. Placement is [get_prec]'s business — a hinted instruction binds at the
loosest precedence, so a tighter context parenthesizes it. *)lethint_attrspp(i:_Ast.instr)=leth=i.hintsinOption.iter(fun(b:boolWax_wasm.Hints.hint)->branch_hint_attrppb.value)h.Wax_wasm.Hints.branch;Option.iter(fun(f:Wax_wasm.Hints.freqWax_wasm.Hints.hint)->freq_attrppf.value)h.Wax_wasm.Hints.freq;Option.iter(fun(l:(Ast.ident*int)listWax_wasm.Hints.hint)->targets_attrppl.value)h.Wax_wasm.Hints.targets(* 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_patpp(p:identoption)=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.Annot.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.Annot.descinatomic_nodepp(Somefield.info)@@fun()->hvboxpp(fun()->letis_struct=matchtypwithStruct_->true|_->falseinboxpp(fun()->keywordpp"type";spacepp();identifierppnm.Annot.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->(* A non-empty [rec { … }] always breaks across lines, like a block body
(see [block_contents]): every separator is a hard [newline], so the
enclosing box lays the members out vertically even when they would fit
on one line. An empty group stays [rec {}]. *)hvboxpp(fun()->boxpp(fun()->keywordpp"rec";spacepp();punctuationpp"{");ifl<>[]then(indentppindent_level(fun()->List.iter(funt->newlinepp();subtypeppt)l);newlinepp());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.Annot.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)|Ascribed_->(* Printed by the [Cast] case itself: the ascription is the
parenthesized [(e : t)] form, not an [as]. *)assertfalseletbranch_instrinstrppnamelabeli=boxpp~indent:indent_level(fun()->keywordppname;spacepp();identifierpp"'";identifierpplabel.Annot.desc;Option.iter(funi->spacepp();instrBranchppi)i)letbranch_ref_instrinstrppnamelabeltyi=boxpp~indent:indent_level(fun()->keywordppname;spacepp();identifierpp"'";identifierpplabel.Annot.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.Annot.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.Annot.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.Annot.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())letget_prec(i:_Ast.instr)=(* A hint attribute is parsed at the loosest precedence (it takes the whole
expression that follows it), so a hinted instruction in a tighter context has
to be parenthesized however tightly its own [desc] would bind. *)ifnot(Wax_wasm.Hints.is_emptyi.hints)thenInstructionelsematchi.descwith|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_|Sequence_->Atom|ArraySet_|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(* The ascription's own parentheses are its syntax: never re-wrapped. *)|Cast(_,Ascribed_)->Atom|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_->Selectletis_block(i:_Ast.instr)=matchi.descwith|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_preciin(* A hinted instruction starts with its attribute's [#], not with the block's
[{], so it needs none of the block-at-statement-start disambiguation. *)ifnot(Wax_wasm.Hints.is_emptyi.hints)thenfalseelseifprec>actualthenfalseelsematchi.descwith|Block_|Loop_|While_|If_|Try_|TryCatch_|TryTable_|If_annotation_|Dispatch_|Match_->true|Call(i,_)|ArrayGet(i,_)->starts_with_block_precCallAndFieldAccessi|ArraySet(i,_,_)->starts_with_block_precAssignementi|Cast(_,Ascribed_)->false|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.Annot.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.Annot.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()->hint_attrsppi;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"{");(* Like a block body, a non-empty [catch] always lays its arms out
vertically with hard [newline]s (matching [match]/[dispatch]);
an empty [catch] stays [{}]. *)ifarms<>[]then(indentppindent_level(fun()->List.iter(funarm->newlinepp();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 [}] sits outside the [indent]
above so it lands at the catch block's own column. *)newlinepp());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"{");(* Like a block body, a non-empty legacy [catch] always breaks its
handlers across lines with hard [newline]s (matching
[match]/[dispatch]); an empty one stays [{}]. *)ifcatches<>[]||catch_all<>Nonethen(indentppindent_level(fun()->List.iter(fun(tag,block)->newlinepp();hvboxpp(fun()->boxpp(fun()->identifierpptag.Annot.desc;spacepp();punctuationpp"=>";spacepp();punctuationpp"{");letafter=located_block_contentsppblockinclose_blockppafter))catches;Option.iter(funblock->newlinepp();hvboxpp(fun()->boxpp(fun()->operatorpp"_";spacepp();punctuationpp"=>";spacepp();punctuationpp"{");letafter=located_block_contentsppblockinclose_blockppafter))catch_all);(* The break before the closing [}] sits outside the [indent]
above so it lands at the catch block's own column. *)newlinepp());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.Annot.desc;operatorpp"#")t;letlen,s=Wax_utils.Unicode.escape_string~hex_prefix:"x"sinstringpp"\"";stringpp~len:(Somelen)s;stringpp"\""|Ints|Floats->constantpps|Cast(i,Ascribedt)->(* The ascription's own parentheses ARE its syntax, so it never needs the
precedence-driven pair the [Cast] level would add. *)boxpp~indent:indent_level(fun()->punctuationpp"(";instrInstructionppi;punctuationpp" :";spacepp();boxpp(fun()->valtypeppt);punctuationpp")")|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.Annot.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|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)=(* A block statement needs no [;] — its [}] ends it — hinted or not: a
statement-level hint prefixes the statement list, so what follows the
attributes is a plain block statement. *)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
[{}] on its own, but emits a zero-width [cut] so that when the enclosing
box breaks anyway (e.g. an empty [if] body beside a multi-line [else]) the
[}] falls to the next line instead of the head's [{] wrapping oddly. *)ifl<>[]then(indentppindent_level(fun()->List.iter(funi->newlinepp();deliminated_instrppi)l);newlinepp())elsecutpp()(* 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())else(* An empty body stays [{}] on its own, but the zero-width [cut] lets the
[}] drop to the next line when the enclosing box breaks anyway (e.g. an
empty [if]/[try] body beside a multi-line [else]/[catch]) rather than the
head's opening [{] wrapping to its own line. *)cutpp();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.Annot.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.Annot.desc;spacepp())typ);Option.iter(funty->raw_functypeppty)sign)letprint_attribute_genopen_pp({attr_name=name;attr_value=i;attr_guard=guard;_}:Ast.attribute)=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.Annot.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.Annot.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))letrecmodulefieldpp(field:(_modulefield,location)Ast.annotated)=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.Annot.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. *)letbranch(b:((locationmodulefield,location)Ast.annotatedlist,location)Ast.annotated)=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 ***)(* The one traversal, shared by the real render ({!module_}) and the dry
trivia-collection pass ({!collect}, which sets [collect]). *)letrender?(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_triviapptailletmodule_?color?out_channel?tailprinter~trivial=render?color?out_channel?tailprinter~trivial(* Dry pass: record the looked-up locations without emitting anything. The Wax
printer streams straight from the AST — there is no intermediate document to
walk, as {!Wax_wasm.Output.collect} does — so this is {!render} driven by the
discarding printer, whose only effect is the [collect] side table
{!Wax_utils.Trivia.get} fills. Width is irrelevant: the traversal looks up the
same locations however the lines break. *)letcollectlset=Wax_utils.Printer.run_discard(funp->renderp~trivia:(Wax_utils.Trivia.empty())~collect:setl)(* 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:(Wax_utils.Trivia.empty())();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