123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159(*
* Copyright (c) 2018 Thomas Gazagnaire <thomas@gazagnaire.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)letsrc=Logs.Src.create"ocaml-mdx"moduleLog=(valLogs.src_logsrc:Logs.LOG)openAstringtypet={(* TODO: move vpad and hpad to `toplevel_tests` *)vpad:int;hpad:int;pos:Lexing.position;command:stringlist;output:Output.tlist;}typetoplevel_tests ={tests:tlist;end_pad:stringoption}letdump_lineppf=function|#Output.taso->Output.dumpppfo|`Command(c,_)->Fmt.pfppf"`Command %a"Fmt.Dump.(liststring)cletdump_lines=Fmt.Dump.listdump_lineletdumpppf {vpad;hpad;command;output;_}=Fmt.pfppf"@[{vpad=%d;@ hpad=%d;@ command=%a;@ output=%a}@]" vpadhpadFmt.Dump.(list string)command(Fmt.Dump.listOutput.dump)outputletdump_toplevel_testsppf{tests;end_pad}=Fmt.pfppf"@[{tests=%a;@ end_pad=%a}@]"Fmt.Dump.(listdump)testsFmt.Dump.(optionstring)end_padletpp_vpadppft=letrecaux=function|0->()|i->Fmt.pfppf"\n";aux(i-1)inauxt.vpad(* somewhat idiosyncratic version of Fmt.list *)letrecpp_list_string_nonblank~sep~blankppf=function|[]->()|[s]->Fmt.stringppfs|x::y::xs->Fmt.stringppf x;letcurrent_sep =matchUtil.String.all_blankywithtrue->blank|false->sepincurrent_sepppf();pp_list_string_nonblank ~sep~blankppf(y::xs)letpp_commandppf(t:t)=matcht.command with|[]->()|l->pp_vpadppft;letsepppf()=Fmt.pfppf"\n%a "Pp.pp_padt.hpadinletblank =Fmt.any"\n"inFmt.pfppf"%a# %a"Pp.pp_padt.hpad(pp_list_string_nonblank ~sep~blank)llet ppppf (t:t)=pp_commandppft;Fmt.stringppf"\n";Pp.pp_lines (Output.pp~pad:t.vpad)ppft.outputletlexbuf~(pos:Lexing.position)s=letlexbuf=Lexing.from_stringsinlexbuf.lex_start_p<-pos;lexbuf.lex_curr_p<-pos;lexbufletvpad_of_linest=letrecauxi=function|`Output h::twhenUtil.String.all_blankh->aux(i+1)t|t->(i,t)inaux0tletrecend_pad_of_lines=function|[]->([],None)|[x;end_pad]whenUtil.String.all_blankend_pad->([x],Some end_pad)|x::xs->letxs,end_pad=end_pad_of_linesxsin(x::xs,end_pad)letunpadhpadline=matchUtil.String.all_blanklinewith|true->line|false->ifString.lengthline<hpadthenFmt.failwith"invalid padding: %S" lineelseString.with_index_range line~first:hpadletrechpad_of_lines=function|[]->0|h::hs->(matchUtil.String.all_blankhwith|true->hpad_of_lineshs|false->leti=ref0inwhile!i<String.lengthh&&h.[!i]=' 'doincridone;!i)letof_lines~(loc:Location.t)t=let pos=loc.loc_startin(* Location.t considers the first line to be 1, whereas the tokenizer
assumes lines start with 0. *)letpos={poswithpos_lnum=pos.pos_lnum-1}inlet hpad=hpad_of_linestinlett,end_pad=end_pad_of_linestinletlines=List.map(unpadhpad)tinlet lines=String.concat~sep:"\n"linesinletlxbuf=lexbuf ~poslinesinletlines=Lexer_top.tokenlxbufinletvpad,lines=vpad_of_lineslinesinLog.debug(funl->l"Toplevel.of_lines (vpad=%d, hpad=%d) %a"vpadhpaddump_lineslines);letmkvpad(command,(loc:Location.t))output={vpad;hpad;pos=loc.loc_start;command;output =List.revoutput }inletrec auxvpadcommand_locoutputacc=function|[]-> List.rev(mkvpadcommand_locoutput::acc)|(`Ellipsis aso)::t->auxvpadcommand_loc(o::output)acct|(`Output_aso)::t->auxvpadcommand_loc(o::output)acct|`Commandcmd::t->letvpad',output=vpad_of_linesoutput inauxvpad'cmd[](mkvpadcommand_locoutput::acc)tinlettests=matchlineswith|`Commandcmd::t->auxvpadcmd[][]t|_->Fmt.failwith"invalid toplevel block: %a"Fmt.(Dump.liststring)tin{tests;end_pad }