12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040moduleMemo_tbl=Hashtbl.Make(structtypet=int*int(* id of parser, position *)letequal((a,b):t)(c,d)=a=c&&b=dlethash=Hashtbl.hashend)moduleMemo_state=struct(* table of closures, used to implement universal type *)typet=(unit->unit)Memo_tbl.t(* unique ID for each parser *)letid_=ref0end(* state common to all parser instances *)typecommon_state={str:string;mutableline_offsets:intarrayoption;mutablememo:Memo_state.toption;}typeposition={pos_cs:common_state;pos_offset:int;mutablepos_lc:(int*int)option;}modulePosition=structtypet=positionletcompute_line_offsets_(s:string):intarray=letlines=CCVector.create()inleti=ref0inCCVector.pushlines0;while!i<String.lengthsdomatchString.index_froms!i'\n'with|exceptionNot_found->i:=String.lengths|j->CCVector.pushlinesj;i:=j+1done;CCVector.to_arraylinesletline_offsets_cs=matchcs.line_offsetswith|Somelines->lines|None->letlines=compute_line_offsets_cs.strincs.line_offsets<-Somelines;linesletint_cmp_:int->int->int=compare(* TODO: use pos_cs.line_offsets *)(* actually re-compute line and column from the buffer *)letcompute_line_and_col_(cs:common_state)(off:int):int*int=letoffsets=line_offsets_csinassert(offsets.(0)=0);matchCCArray.bsearch~cmp:int_cmp_offoffsetswith|`At0->0,0|`Atn->n-1,off-offsets.(n-1)-1|`Just_aftern->n,off-offsets.(n)|`Empty->assertfalse|`All_bigger->assertfalse(* off >= 0, and offsets[0] == 0 *)|`All_lower->letn=Array.lengthoffsets-1inn,off-offsets.(n)letline_and_columnself=matchself.pos_lcwith|Sometup->tup|None->lettup=compute_line_and_col_self.pos_csself.pos_offsetinself.pos_lc<-Sometup;(* save *)tupletlineself=fst(line_and_columnself)letcolumnself=snd(line_and_columnself)letppoutself=letl,c=line_and_columnselfinFormat.fprintfout"at line %d, column %d"lcendmoduleError=structtypet={msg:unit->string;pos:position;}letpositionself=self.posletline_and_columnself=Position.line_and_columnself.posletmsgself=self.msg()letto_stringself=letline,col=line_and_columnselfinPrintf.sprintf"at line %d, char %d: %s"linecol(self.msg())letppoutself=letline,col=line_and_columnselfinFormat.fprintfout"@[<hv>at line %d, char %d:@ %s@]"linecol(self.msg())endtype+'aor_error=('a,Error.t)resulttypestate={cs:common_state;i:int;(* offset in [str] *)j:int;(* end pointer in [str], excluded. [len = j-i] *)}(** Purely functional state passed around *)(* FIXME: replace memo with:
[global : global_st ref]
where:
[type global = {
mutable memo: Memo_state.t option;
line_offsets: int CCVector.vector;
}
with line_offsets used to cache the offset where each line begins,
and is computed lazily, to make {!Position.line_and_column}
faster if called many times.
*)let[@inline]char_equal(a:char)b=Stdlib.(=)abletstring_equal=String.equal(* FIXME: printer for error
let () = Printexc.register_printer
(function
| ParseError (b,msg) ->
Some (Format.sprintf "@[<v>%s@ %s@]" (msg()) (string_of_branch b))
| _ -> None)
*)let[@inline]const_str_x():string=xletstate_of_stringstr=lets={cs={str;memo=None;line_offsets=None};i=0;j=String.lengthstr;}inslet[@inline]is_donest=st.i>=st.jlet[@inline]curst=st.cs.str.[st.i]letpos_of_st_st:position={pos_cs=st.cs;pos_offset=st.i;pos_lc=None}letmk_error_stmsg:Error.t={Error.msg;pos=pos_of_st_st}(* consume one char, passing it to [ok]. *)letconsume_st~ok~err=ifis_donestthen(letmsg=const_str_"unexpected end of input"inerr(mk_error_stmsg))else(letc=st.cs.str.[st.i]inok{stwithi=st.i+1}c)type'at={run:'b.state->ok:(state->'a->'b)->err:(Error.t->'b)->'b;}[@@unboxed](** Takes the input and two continuations:
{ul
{- [ok] to call with the result and new state when it's done}
{- [err] to call when the parser met an error}
}
*)letreturnx:_t={run=(funst~ok~err:_->okstx)}letpure=returnletmapf(p:'at):_t={run=(funst~ok~err->p.runst~ok:(funstx->okst(fx))~err)}letbindf(p:'at):_t={run=(funst~ok~err->p.runst~ok:(funstx->letp2=fxinp2.runst~ok~err)~err);}letap(f:_t)(a:_t):_t={run=(funst~ok~err->f.runst~ok:(funstf->a.runst~ok:(funstx->okst(fx))~err)~err);}letap_left(a:_t)(b:_t):_t={run=(funst~ok~err->a.runst~ok:(funstx->b.runst~ok:(funst_->okstx)~err)~err);}letap_right(a:_t)(b:_t):_t={run=(funst~ok~err->a.runst~ok:(funst_->b.runst~ok:(funstx->okstx)~err)~err);}letor_(p1:'at)(p2:'at):_t={run=(funst~ok~err->p1.runst~ok~err:(fun_e->p2.runst~ok~err));}letbothab={run=(funst~ok~err->a.runst~ok:(funstxa->b.runst~ok:(funstxb->okst(xa,xb))~err)~err);}letset_error_messagemsg(p:'at):_t={run=(funst~ok~err->p.runst~ok~err:(fun_e->err(mk_error_st(const_str_msg))));}moduleInfix=structlet[@inline](>|=)pf=mapfplet[@inline](>>=)pf=bindfplet(<*>)=aplet(<*)=ap_leftlet(*>)=ap_rightlet(<|>)=or_let(|||)=bothlet[@inline](<?>)pmsg=set_error_messagemsgplet(let+)=(>|=)let(let*)=(>>=)let(and+)=bothlet(and*)=(and+)endincludeInfixletmap2fxy=puref<*>x<*>yletmap3fxyz=puref<*>x<*>y<*>zletjunk_(st:state):state=assert(st.i<st.j);{stwithi=st.i+1}leteoi={run=(funst~ok~err->ifis_donestthenokst()elseerr(mk_error_st(const_str_"expected end of input")));}letwith_posp:_t={run=(funst~ok~err->p.runst~ok:(funst'x->okst'(x,pos_of_st_st))~err);}letpos:_t={run=(funst~ok~err:_->okst(pos_of_st_st))}(* a slice is just a state, which makes {!recurse} quite easy. *)typeslice=statemoduleSlice=structtypet=sliceletlengthsl=sl.j-sl.iletis_emptysl=sl.i=sl.jletto_stringsl=String.subsl.cs.strsl.i(lengthsl)endletrecurseslicep:_t={run=(funst~ok~err->(* make sure these states are related. all slices share the
same reference as the initial state they derive from. *)assert(Stdlib.(st.cs==slice.cs));p.runslice~ok:(fun_stx->okstx)~err);}letall={run=(funst~ok~err:_->ifis_donestthenokststelse(letst_done={stwithi=st.j}inokst_donest));}letall_str=all>|=Slice.to_stringletfailmsg:_t={run=(funst~ok:_~err->err(mk_error_st(const_str_msg)))}letfailfmsg=Printf.ksprintffailmsgletfail_lazymsg={run=(funst~ok:_~err->err(mk_error_stmsg))}letparsingwhatp={run=(funst~ok~err->p.runst~ok~err:(fune->letmsg()=Printf.sprintf"while parsing %s:\n%s"what(e.Error.msg())inerr{ewithError.msg}));}letempty={run=(funst~ok~err:_->okst())}letany_char={run=(funst~ok~err->consume_st~ok~err)}letcharc:_t={run=(funst~ok~err->consume_st~ok:(funstc2->ifchar_equalcc2thenokstcelse(letmsg()=Printf.sprintf"expected '%c', got '%c'"cc2inerr(mk_error_stmsg)))~err);}letchar_if?descrp={run=(funst~ok~err->consume_st~ok:(funstc->ifpcthenokstcelse(letmsg()=letrest=matchdescrwith|None->""|Somed->Printf.sprintf", expected %s"dinPrintf.sprintf"unexpected char '%c'%s"crestinerr(mk_error_stmsg)))~err);}lettake_ifp:slicet={run=(funst~ok~err:_->leti=refst.iinwhileletst={stwithi=!i}in(not(is_donest))&&p(curst)doincridone;ok{stwithi=!i}{stwithj=!i});}lettake1_if?descrp=take_ifp>>=funsl->ifSlice.is_emptyslthen(letmsg()=letwhat=matchdescrwith|None->""|Somed->Printf.sprintf" for %s"dinPrintf.sprintf"expected non-empty sequence of chars%s"whatinfail_lazymsg)elsereturnslletchars_ifp=take_ifp>|=Slice.to_stringletchars1_if?descrp={run=(funst~ok~err->(chars_ifp).runst~ok:(funsts->ifstring_equals""then(letmsg()=letwhat=matchdescrwith|None->""|Somed->Printf.sprintf" for %s"dinPrintf.sprintf"expected non-empty sequence of chars%s"whatinerr(mk_error_stmsg))elseoksts)~err);}exceptionFold_failofstate*stringletchars_fold~facc0={run=(funst~ok~err->leti0=st.iinleti=refi0inletacc=refacc0inletcontinue=reftrueintrywhile!continuedoletst={stwithi=!i}inifis_donestthencontinue:=falseelse(letc=curstinmatchf!acccwith|`Continueacc'->incri;acc:=acc'|`Stopa->acc:=a;continue:=false|`Consume_and_stopa->acc:=a;incri;continue:=false|`Failmsg->raise(Fold_fail(st,msg)))done;ok{stwithi=!i}(!acc,{stwithj=!i})withFold_fail(st,msg)->err(mk_error_st(const_str_msg)));}letchars_fold_transduce~facc0={run=(funst~ok~err->leti0=st.iinleti=refi0inletacc=refacc0inletcontinue=reftrueinletbuf=Buffer.create16intrywhile!continuedoletst={stwithi=!i}inifis_donestthencontinue:=falseelse(letc=curstinmatchf!acccwith|`Continueacc'->incri;acc:=acc'|`Yield(acc',c')->incri;acc:=acc';Buffer.add_charbufc'|`Stop->continue:=false|`Consume_and_stop->incri;continue:=false|`Failmsg->raise(Fold_fail(st,msg)))done;ok{stwithi=!i}(!acc,Buffer.contentsbuf)withFold_fail(st,msg)->err(mk_error_st(const_str_msg)));}letskip_charsp:_t=letrecself={run=(funst~ok~err->if(not(is_donest))&&p(curst)then(letst=junk_stinself.runst~ok~err)elseokst());}inselfletis_alpha=function|'a'..'z'|'A'..'Z'->true|_->falseletis_num=function|'0'..'9'->true|_->falseletis_alpha_num=function|'a'..'z'|'A'..'Z'|'0'..'9'->true|_->falseletis_space=function|' '|'\t'->true|_->falseletis_white=function|' '|'\t'|'\n'->true|_->falseletspace=char_ifis_spaceletwhite=char_ifis_whiteletendline=char_if~descr:"end-of-line ('\\n')"(function|'\n'->true|_->false)letskip_space=skip_charsis_spaceletskip_white=skip_charsis_whitelettry_orp1~f~else_:p2={run=(funst~ok~err->p1.runst~ok:(funstx->(fx).runst~ok~err)~err:(fun_->p2.runst~ok~err));}lettry_or_l?(msg="try_or_l ran out of options")?else_l:_t={run=(funst~ok~err->letrecloop=function|(test,p)::tl->test.runst~ok:(fun__->p.runst~ok~err)(* commit *)~err:(fun_->looptl)|[]->(matchelse_with|None->err(mk_error_st(const_str_msg))|Somep->p.runst~ok~err)inloopl);}letsuspendf={run=(funst~ok~err->letp=f()inp.runst~ok~err);}(* read [len] chars at once *)lettakelen:slicet={run=(funst~ok~err->ifst.i+len<=st.jthen(letslice={stwithj=st.i+len}inletst={stwithi=st.i+len}inokstslice)else(letmsg()=Printf.sprintf"expected to be able to consume %d chars"leninerr(mk_error_stmsg)));}lettake_until_successp:(slice*_)t={run=(funst~ok~err->leti=refst.iinletst_after_p=refstinletcontinue=reftrueinletres=refNoneinwhile!continue&&!i<=st.jdoletst'={stwithi=!i}inp.runst'~ok:(funnew_stx->(* success *)res:=Somex;continue:=false;(* parsing will continue where [p] left off *)st_after_p:=new_st)~err:(fun_->incri)done;match!reswith|None->err(mk_error_st(const_str_"take_until_success: no position worked"))|Somex->letslice={stwithj=!i}inok!st_after_p(slice,x));}letany_char_nlen:_t=takelen>|=Slice.to_stringletexacts={run=(funst~ok~err->(* parse a string of length [String.length s] and compare with [s] *)(any_char_n(String.lengths)).runst~ok:(funsts2->ifstring_equalss2thenokstselse(letmsg()=Printf.sprintf"expected %S, got %S"ss2inerr(mk_error_stmsg)))~err);}letstring=exactletfixf=letrecself={run=(funst~ok~err->(Lazy.forcef_self).runst~ok~err)}andf_self=lazy(fself)inselflettry_p=plettry_optp:_t={run=(funst~ok~err:_->p.runst~ok:(funstx->okst(Somex))~err:(fun_->okstNone));}letoptionalp:_t={run=(funst~ok~err:_->p.runst~ok:(funst_x->okst())~err:(fun_->okst()));}letmany_until~untilp:_t=fix(funself->try_oruntil~f:(fun_->pure[])~else_:(p>>=funx->self>|=funl->x::l))letmanyp:_t=fix(funself->try_orp~f:(funx->self>|=funtl->x::tl)~else_:(pure[]))(*
(* parse many [p], as a difference list *)
let many_rec_ p : (_ list -> _ list) t =
let rec self = {
run=fun st ~ok ~err ->
if is_done st then ok st (fun l->l) (* empty list *)
else (
p.run st
~ok:(fun st x ->
self.run st
~ok:(fun st f -> ok st (fun l -> x :: f l))
~err)
~err
)
} in
self
let many p : _ t = {
run=fun st ~ok ~err ->
(many_rec_ p).run st
~ok:(fun st f -> ok st (f []))
~err
}
*)letmany1p=p>>=funx->manyp>|=funl->x::l(* skip can be made efficient by not allocating intermediate parsers *)letskipp:_t=letrecself={run=(funst~ok~err->p.runst~ok:(funst_->self.runst~ok~err)~err:(fun_->okst()));}inselfletsep_until~until~byp=letrecread_p=lazy(p>>=funx->until*>pure[x]<|>by*>(Lazy.forceread_p>|=funtl->x::tl))inuntil*>pure[]<|>Lazy.forceread_pletsep~byp=letrecread_p=lazy(try_orp~f:(funx->eoi*>pure[x]<|>try_orby~f:(fun_->Lazy.forceread_p>|=funtl->x::tl)~else_:(pure[x]))~else_:(pure[]))inLazy.forceread_pletsep1~byp=p>>=funx->sep~byp>|=funtl->x::tlletlookaheadp:_t={run=(funst~ok~err->p.runst~ok:(fun_stx->okstx)(* discard p's new state *)~err);}letlookahead_ignorep:_t={run=(funst~ok~err->p.runst~ok:(fun_st_x->okst())~err)}letset_current_slicesl:_t={run=(fun_st~ok~err:_->assert(Stdlib.(_st.cs==sl.cs));oksl())(* jump to slice *);}letsplit_1~on_char:_t={run=(funst~ok~err:_->ifst.i>=st.jthenokst(st,None)else(letret_empty()=letst_done={stwithi=st.j}in(* empty *)okst_done(st,None)inmatchString.index_fromst.cs.strst.ion_charwith|j->ifj<=st.jthen(letx={stwithj}inlety={stwithi=minst.j(j+1)}inletst_done={stwithi=st.j}in(* empty *)okst_done(x,Somey))elseret_empty()|exceptionNot_found->ret_empty()));}letsplit_list_at_most~on_charn:slicelistt=letrecloopaccn=ifn<=0then(* add the rest to [acc] *)all>|=funrest->letacc=rest::accinList.revaccelsetry_oreoi~f:(fun_->return(List.revacc))~else_:(parse_1accn)andparse_1accn=split_1~on_char>>=fun(sl1,rest)->letacc=sl1::accinmatchrestwith|None->return(List.revacc)|Somerest->recurserest(loopacc(n-1))inloop[]nletsplit_2~on_char:_t=split_list_at_most~on_char3>>=function|[a;b]->return(a,b)|_->fail"split_2: expected 2 fields exactly"letsplit_3~on_char:_t=split_list_at_most~on_char4>>=function|[a;b;c]->return(a,b,c)|_->fail"split_3: expected 3 fields exactly"letsplit_4~on_char:_t=split_list_at_most~on_char5>>=function|[a;b;c;d]->return(a,b,c,d)|_->fail"split_4: expected 4 fields exactly"letsplit_list~on_char:slicelistt=letrecloopacc=try_oreoi~f:(fun_->return(List.revacc))~else_:(parse_1acc)andparse_1acc=split_1~on_char>>=fun(sl1,rest)->letacc=sl1::accinmatchrestwith|None->return(List.revacc)|Somerest->recurserest(loopacc)inloop[]leteach_split~on_charp:'alistt=letrecloopacc=split_1~on_char>>=fun(sl1,rest)->(* parse [sl1] with [p] *)recursesl1p>>=funx->letacc=x::accinmatchrestwith|None->return(List.revacc)|Somerest->recurserest(loopacc)inloop[]letline:slicet=split_1~on_char:'\n'>>=fun(sl,rest)->matchrestwith|None->returnsl|Somerest->set_current_slicerest>|=fun()->slletline_str=line>|=Slice.to_stringleteach_linep:_t=each_split~on_char:'\n'pletmemo(typea)(p:at):at=letid=!Memo_state.id_inincrMemo_state.id_;letr=refNonein(* used for universal encoding *){run=(funst~ok~err->lettbl=matchst.cs.memowith|Somet->t|None->lettbl=Memo_tbl.create32inst.cs.memo<-Sometbl;tblinmatchr:=None;letf=Memo_tbl.findtbl(st.i,id)inf();!rwith|None->assertfalse|Some(Ok(st,x))->okstx|Some(Errore)->erre|exceptionNot_found->(* parse, and save *)p.runst~ok:(funst'x->Memo_tbl.replacetbl(st.i,id)(fun()->r:=Some(Ok(st',x)));okst'x)~err:(fune->Memo_tbl.replacetbl(st.i,id)(fun()->r:=Some(Errore));erre));}letfix_memof=letrecp={run=(funst~ok~err->(Lazy.forcep').runst~ok~err)}andp'=lazy(memo(fp))inpexceptionParseErrorofError.tletstringify_result=function|Ok_asx->x|Errore->Error(Error.to_stringe)letparse_string_exnps=p.run(state_of_strings)~ok:(fun_stx->x)~err:(fune->raise(ParseErrore))letparse_string_eps=p.run(state_of_strings)~ok:(fun_stx->Okx)~err:(fune->Errore)letparse_stringps=parse_string_eps|>stringify_resultletread_all_ic=letbuf=Buffer.create1024in(trywhiletruedoletline=input_lineicinBuffer.add_stringbufline;Buffer.add_charbuf'\n'done;assertfalsewithEnd_of_file->());Buffer.contentsbufletparse_file_epfile=letic=open_infileinlets=read_all_icinletr=parse_string_epsinclose_inic;rletparse_filepfile=parse_file_epfile|>stringify_resultletparse_file_exnpfile=matchparse_file_epfilewith|Okx->x|Errore->raise(ParseErrore)moduleU=structletlist?(start="[")?(stop="]")?(sep=";")p=stringstart*>skip_white*>sep_until~until:(skip_white<*stringstop)~by:(skip_white*>stringsep*>skip_white)pletint=skip_white*>chars1_if~descr:"integer"(func->is_numc||char_equalc'-')>>=funs->tryreturn(int_of_strings)withFailure_->fail"expected an int"letin_paren(p:'at):'at=skip_white*>(char'('*>skip_white*>p<*skip_white<*char')')letin_parens_opt(p:'at):'at=fix(funself->skip_white*>try_or(char'(')~f:(fun_->skip_white*>self<*skip_white<*char')')~else_:p)letoptionp=skip_white*>try_or(string"Some")~f:(fun_->skip_white*>p>|=funx->Somex)~else_:(string"None"*>returnNone)lethexa_int=(exact"0x"<|>return"")*>(chars1_if(function|'0'..'9'|'a'..'f'|'A'..'F'->true|_->false)>|=funs->leti=ref0inString.iter(func->letn=matchcwith|'0'..'9'->Char.codec-Char.code'0'|'a'..'f'->Char.codec-Char.code'a'+10|'A'..'F'->Char.codec-Char.code'A'+10|_->assertfalseini:=(!i*16)+n)s;!i)letprepend_strcs=String.make1c^sletword=map2prepend_str(char_ifis_alpha)(chars_ifis_alpha_num)letbool=skip_white*>(string"true"*>returntrue<|>string"false"*>returnfalse)letpair?(start="(")?(stop=")")?(sep=",")p1p2=skip_white*>stringstart*>skip_white*>p1>>=funx1->skip_white*>stringsep*>skip_white*>p2>>=funx2->skip_white*>stringstop*>return(x1,x2)lettriple?(start="(")?(stop=")")?(sep=",")p1p2p3=stringstart*>skip_white*>p1>>=funx1->skip_white*>stringsep*>skip_white*>p2>>=funx2->skip_white*>stringsep*>skip_white*>p3>>=funx3->stringstop*>return(x1,x2,x3)endmoduleDebug_=structlettrace_failnamep={run=(funst~ok~err->p.runst~ok~err:(fune->Printf.eprintf"trace %s: fail with %s\n%!"name(Error.to_stringe);erre));}lettrace_~bothname~printp={run=(funst~ok~err->p.runst~ok:(funstx->Printf.eprintf"trace %s: parsed %s\n%!"name(printx);okstx)~err:(fune->ifboththenPrintf.eprintf"trace %s: fail with %s\n%!"name(Error.to_stringe);erre));}lettrace_successname~printp=trace_~both:falsename~printplettrace_success_or_failname~printp=trace_~both:truename~printpend