123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026(**************************************************************************)(* *)(* OCaml *)(* *)(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)(* *)(* Copyright 1996 Institut National de Recherche en Informatique et *)(* en Automatique. *)(* *)(* All rights reserved. This file is distributed under the terms of *)(* the GNU Lesser General Public License version 2.1, with the *)(* special exception on linking described in the file LICENSE. *)(* *)(**************************************************************************)openLexingtypet=Warnings.loc={loc_start:position;loc_end:position;loc_ghost:bool}letin_file=Warnings.ghost_loc_in_fileletnone=in_file"_none_"letis_nonel=(l=none)letcurrlexbuf={loc_start=lexbuf.lex_start_p;loc_end=lexbuf.lex_curr_p;loc_ghost=false}letinitlexbuffname=lexbuf.lex_curr_p<-{pos_fname=fname;pos_lnum=1;pos_bol=0;pos_cnum=0;}letsymbol_rloc()={loc_start=Parsing.symbol_start_pos();loc_end=Parsing.symbol_end_pos();loc_ghost=false;}letsymbol_gloc()={loc_start=Parsing.symbol_start_pos();loc_end=Parsing.symbol_end_pos();loc_ghost=true;}letrhs_locn={loc_start=Parsing.rhs_start_posn;loc_end=Parsing.rhs_end_posn;loc_ghost=false;}letrhs_intervalmn={loc_start=Parsing.rhs_start_posm;loc_end=Parsing.rhs_end_posn;loc_ghost=false;}(* return file, line, char from the given position *)letget_pos_infopos=(pos.pos_fname,pos.pos_lnum,pos.pos_cnum-pos.pos_bol)type'aloc={txt:'a;loc:t;}letmkloctxtloc={txt;loc}letmknoloctxt=mkloctxtnone(******************************************************************************)(* Input info *)letinput_name=ref"_none_"letinput_lexbuf=ref(None:lexbufoption)letinput_phrase_buffer=ref(None:Buffer.toption)(** Call [Location.init] and set the input info. *)letinit_infolexbuffname=input_name:=fname;input_lexbuf:=Somelexbuf;initlexbuffname(******************************************************************************)(* Terminal info *)letstatus=refTerminfo.Uninitialisedletsetup_terminal()=if!status=Terminfo.Uninitialisedthenstatus:=Terminfo.setupstdout(* The number of lines already printed after input.
This is used by [highlight_terminfo] to identify the current position of the
input in the terminal. This would not be possible without this information,
since printing several warnings/errors adds text between the user input and
the bottom of the terminal.
We also use for {!is_first_report}, see below.
*)letnum_loc_lines=ref0(* We use [num_loc_lines] to determine if the report about to be
printed is the first or a follow-up report of the current
"batch" -- contiguous reports without user input in between, for
example for the current toplevel phrase. We use this to print
a blank line between messages of the same batch.
*)letis_first_message()=!num_loc_lines=0(* This is used by the toplevel to reset [num_loc_lines] before each phrase *)letreset()=num_loc_lines:=0(* This is used by the toplevel *)letecho_eof()=print_newline();incrnum_loc_lines(* Code printing errors and warnings must be wrapped using this function, in
order to update [num_loc_lines].
[print_updating_num_loc_lines ppf f arg] is equivalent to calling [f ppf
arg], and additionally updates [num_loc_lines]. *)letprint_updating_num_loc_linesppffarg=letopenFormatinletout_functions=pp_get_formatter_out_functionsppf()inletout_stringstrstartlen=letreccountic=ifi=start+lenthencelseifString.getstri='\n'thencount(succi)(succc)elsecount(succi)cinnum_loc_lines:=!num_loc_lines+countstart0;out_functions.out_stringstrstartleninpp_set_formatter_out_functionsppf{out_functionswithout_string};fppfarg;pp_print_flushppf();pp_set_formatter_out_functionsppfout_functions(** {1 Printing setup }*)letsetup_tags()=Misc.Style.setup!Clflags.color(******************************************************************************)(* Printing locations, e.g. 'File "foo.ml", line 3, characters 10-12' *)(* [Build_path_prefix_map] related features are disabled in ocamlformat
let rewrite_absolute_path path =
match Misc.get_build_path_prefix_map () with
| None -> path
| Some map -> Build_path_prefix_map.rewrite map path
let rewrite_find_first_existing path =
match Misc.get_build_path_prefix_map () with
| None ->
if Sys.file_exists path then Some path
else None
| Some prefix_map ->
match Build_path_prefix_map.rewrite_all prefix_map path with
| [] ->
if Sys.file_exists path then Some path
else None
| matches ->
Some (List.find Sys.file_exists matches)
let rewrite_find_all_existing_dirs path =
let ok path = Sys.file_exists path && Sys.is_directory path in
match Misc.get_build_path_prefix_map () with
| None ->
if ok path then [path]
else []
| Some prefix_map ->
match Build_path_prefix_map.rewrite_all prefix_map path with
| [] ->
if ok path then [path]
else []
| matches ->
match (List.filter ok matches) with
| [] -> raise Not_found
| results -> results
let absolute_path s = (* This function could go into Filename *)
let open Filename in
let s = if (is_relative s) then (concat (Sys.getcwd ()) s) else s in
let s = rewrite_absolute_path s in
(* Now simplify . and .. components *)
let rec aux s =
let base = basename s in
let dir = dirname s in
if dir = s then dir
else if base = current_dir_name then aux dir
else if base = parent_dir_name then dirname (aux dir)
else concat (aux dir) base
in
aux s
let show_filename file =
if !Clflags.absname then absolute_path file else file
*)letshow_filenamefile=filemoduleFmt=Format_docmoduleDoc=struct(* This is used by the toplevel and the report printers below. *)letseparate_new_messageppf()=ifnot(is_first_message())thenbeginFmt.pp_print_newlineppf();incrnum_loc_linesendletfilenameppffile=Fmt.pp_print_stringppf(show_filenamefile)(* Best-effort printing of the text describing a location, of the form
'File "foo.ml", line 3, characters 10-12'.
Some of the information (filename, line number or characters numbers) in the
location might be invalid; in which case we do not print it.
*)letlocppfloc=setup_tags();letfile_valid=function|"_none_"->(* This is a dummy placeholder, but we print it anyway to please
editors that parse locations in error messages (e.g. Emacs). *)true|""|"//toplevel//"->false|_->trueinletline_validline=line>0inletchars_valid~startchar~endchar=startchar<>-1&&endchar<>-1inletfile=(* According to the comment in location.mli, if [pos_fname] is "", we must
use [!input_name]. *)ifloc.loc_start.pos_fname=""then!input_nameelseloc.loc_start.pos_fnameinletstartline=loc.loc_start.pos_lnuminletendline=loc.loc_end.pos_lnuminletstartchar=loc.loc_start.pos_cnum-loc.loc_start.pos_bolinletendchar=loc.loc_end.pos_cnum-loc.loc_end.pos_bolinletfirst=reftrueinletcapitalizes=if!firstthen(first:=false;String.capitalize_asciis)elsesinletcomma()=if!firstthen()elseFmt.fprintfppf", "inFmt.fprintfppf"@{<loc>";iffile_validfilethenFmt.fprintfppf"%s \"%a\""(capitalize"file")filenamefile;(* Print "line 1" in the case of a dummy line number. This is to please the
existing setup of editors that parse locations in error messages (e.g.
Emacs). *)comma();letstartline=ifline_validstartlinethenstartlineelse1inletendline=ifline_validendlinethenendlineelsestartlineinbeginifstartline=endlinethenFmt.fprintfppf"%s %i"(capitalize"line")startlineelseFmt.fprintfppf"%s %i-%i"(capitalize"lines")startlineendlineend;ifchars_valid~startchar~endcharthen(comma();Fmt.fprintfppf"%s %i-%i"(capitalize"characters")startcharendchar);Fmt.fprintfppf"@}"(* Print a comma-separated list of locations *)letlocsppflocs=Fmt.pp_print_list~pp_sep:(funppf()->Fmt.fprintfppf",@ ")locppflocsletquoted_filenameppff=Misc.Style.as_inline_codefilenameppffendletprint_filename=Fmt.compatDoc.filenameletprint_loc=Fmt.compatDoc.locletprint_locs=Fmt.compatDoc.locsletseparate_new_messageppf=Fmt.compatDoc.separate_new_messageppf()(******************************************************************************)(* An interval set structure; additionally, it stores user-provided information
at interval boundaries.
The implementation provided here is naive and assumes the number of intervals
to be small, but the interface would allow for a more efficient
implementation if needed.
Note: the structure only stores maximal intervals (that therefore do not
overlap).
*)moduleISet:sigtype'abound='a*inttype'at(* bounds are included *)valof_intervals:('abound*'abound)list->'atvalmem:'at->pos:int->boolvalfind_bound_in:'at->range:(int*int)->'aboundoptionvalis_start:'at->pos:int->'aoptionvalis_end:'at->pos:int->'aoptionvalextrema:'at->('abound*'abound)optionend=structtype'abound='a*int(* non overlapping intervals *)type'at=('abound*'abound)listletof_intervalsintervals=letpos=List.map(fun((a,x),(b,y))->ifx>ythen[]else[((a,x),`S);((b,y),`E)])intervals|>List.flatten|>List.sort(fun((_,x),k)((_,y),k')->(* Make `S come before `E so that consecutive intervals get merged
together in the fold below *)letkn=function`S->0|`E->1incompare(x,knk)(y,knk'))inletnesting,acc=List.fold_left(fun(nesting,acc)(a,kind)->matchkind,nestingwith|`S,`Outside->`Inside(a,0),acc|`S,`Inside(s,n)->`Inside(s,n+1),acc|`E,`Outside->assertfalse|`E,`Inside(s,0)->`Outside,((s,a)::acc)|`E,`Inside(s,n)->`Inside(s,n-1),acc)(`Outside,[])posinassert(nesting=`Outside);List.revaccletmemiset~pos=List.exists(fun((_,s),(_,e))->s<=pos&&pos<=e)isetletfind_bound_iniset~range:(start,end_)=List.find_map(fun((a,x),(b,y))->ifstart<=x&&x<=end_thenSome(a,x)elseifstart<=y&&y<=end_thenSome(b,y)elseNone)isetletis_startiset~pos=List.find_map(fun((a,x),_)->ifpos=xthenSomeaelseNone)isetletis_endiset~pos=List.find_map(fun(_,(b,y))->ifpos=ythenSomebelseNone)isetletextremaiset=ifiset=[]thenNoneelseSome(fst(List.hdiset),snd(List.hd(List.reviset)))end(******************************************************************************)(* Toplevel: highlighting and quoting locations *)(* Highlight the locations using standout mode.
If [locs] is empty, this function is a no-op.
*)lethighlight_terminfolbppflocs=Format.pp_print_flushppf();(* avoid mixing Format and normal output *)(* Char 0 is at offset -lb.lex_abs_pos in lb.lex_buffer. *)letpos0=-lb.lex_abs_posin(* Do nothing if the buffer does not contain the whole phrase. *)ifpos0<0thenraiseExit;(* Count number of lines in phrase *)letlines=ref!num_loc_linesinfori=pos0tolb.lex_buffer_len-1doifBytes.getlb.lex_bufferi='\n'thenincrlinesdone;(* If too many lines, give up *)if!lines>=Terminfo.num_linesstdout-2thenraiseExit;(* Move cursor up that number of lines *)flushstdout;Terminfo.backupstdout!lines;(* Print the input, switching to standout for the location *)letbol=reffalseinprint_string"# ";forpos=0tolb.lex_buffer_len-pos0-1doif!bolthen(print_string" ";bol:=false);ifList.exists(funloc->pos=loc.loc_start.pos_cnum)locsthenTerminfo.standoutstdouttrue;ifList.exists(funloc->pos=loc.loc_end.pos_cnum)locsthenTerminfo.standoutstdoutfalse;letc=Bytes.getlb.lex_buffer(pos+pos0)inprint_charc;bol:=(c='\n')done;(* Make sure standout mode is over *)Terminfo.standoutstdoutfalse;(* Position cursor back to original location *)Terminfo.resumestdout!num_loc_lines;flushstdoutlethighlight_terminfolbppflocs=tryhighlight_terminfolbppflocswithExit->()(* Highlight the location by printing it again.
There are two different styles for highlighting errors in "dumb" mode,
depending if the error fits on a single line or spans across several lines.
For single-line errors,
foo the_error bar
gets displayed as follows, where X is the line number:
X | foo the_error bar
^^^^^^^^^
For multi-line errors,
foo the_
error bar
gets displayed as:
X1 | ....the_
X2 | error....
An ellipsis hides the middle lines of the multi-line error if it has more
than [max_lines] lines.
If [locs] is empty then this function is a no-op.
*)typeinput_line={text:string;start_pos:int;}(* Takes a list of lines with possibly missing line numbers.
If the line numbers that are present are consistent with the number of lines
between them, then infer the intermediate line numbers.
This is not always the case, typically if lexer line directives are
involved... *)letinfer_line_numbers(lines:(intoption*input_line)list):(intoption*input_line)list=let(_,offset,consistent)=List.fold_left(fun(i,offset,consistent)(lnum,_)->matchlnum,offsetwith|None,_->(i+1,offset,consistent)|Somen,None->(i+1,Some(n-i),consistent)|Somen,Somem->(i+1,offset,consistent&&n=m+i))(0,None,true)linesinmatchoffset,consistentwith|Somem,true->List.mapi(funi(_,line)->(Some(m+i),line))lines|_,_->lines(* [get_lines] must return the lines to highlight, given starting and ending
positions.
See [lines_around_from_current_input] below for an instantiation of
[get_lines] that reads from the current input.
*)lethighlight_quoteppf~(get_lines:start_pos:position->end_pos:position->input_linelist)?(max_lines=10)highlight_taglocs=letiset=ISet.of_intervals@@List.filter_map(funloc->lets,e=loc.loc_start,loc.loc_endinifs.pos_cnum=-1||e.pos_cnum=-1thenNoneelseSome((s,s.pos_cnum),(e,e.pos_cnum-1)))locsinmatchISet.extremaisetwith|None->()|Some((leftmost,_),(rightmost,_))->letlines=get_lines~start_pos:leftmost~end_pos:rightmost|>List.map(fun({text;start_pos}asline)->letend_pos=start_pos+String.lengthtext-1inletline_nb=matchISet.find_bound_iniset~range:(start_pos,end_pos)with|None->None|Some(p,_)->Somep.pos_lnumin(line_nb,line))|>infer_line_numbers|>List.map(fun(lnum,{text;start_pos})->(text,Option.fold~some:Int.to_string~none:""lnum,start_pos))inFmt.fprintfppf"@[<v>";beginmatchlineswith|[]|[("",_,_)]->()|[(line,line_nb,line_start_cnum)]->(* Single-line error *)Fmt.fprintfppf"%s | %s@,"line_nbline;Fmt.fprintfppf"%*s "(String.lengthline_nb)"";(* Iterate up to [rightmost], which can be larger than the length of
the line because we may point to a location after the end of the
last token on the line, for instance:
{[
token
^
Did you forget ...
]} *)fori=0torightmost.pos_cnum-line_start_cnum-1doletpos=line_start_cnum+iinifISet.is_startiset~pos<>NonethenFmt.fprintfppf"@{<%s>"highlight_tag;ifISet.memiset~posthenFmt.pp_print_charppf'^'elseifi<String.lengthlinethenbegin(* For alignment purposes, align using a tab for each tab in the
source code *)ifline.[i]='\t'thenFmt.pp_print_charppf'\t'elseFmt.pp_print_charppf' 'end;ifISet.is_endiset~pos<>NonethenFmt.fprintfppf"@}"done;Fmt.fprintfppf"@}@,"|_->(* Multi-line error *)Fmt.pp_two_columns~sep:"|"~max_linesppf@@List.map(fun(line,line_nb,line_start_cnum)->letline=String.mapi(funicar->ifISet.memiset~pos:(line_start_cnum+i)thencarelse'.')linein(line_nb,line))linesend;Fmt.fprintfppf"@]"letlines_around~(start_pos:position)~(end_pos:position)~(seek:int->unit)~(read_char:unit->charoption):input_linelist=seekstart_pos.pos_bol;letlines=ref[]inletbol=refstart_pos.pos_bolinletcur=refstart_pos.pos_bolinletb=Buffer.create80inletadd_line()=if!bol<!curthenbeginlettext=Buffer.contentsbinBuffer.clearb;lines:={text;start_pos=!bol}::!lines;bol:=!curendinletrecloop()=if!bol>=end_pos.pos_cnumthen()elsebeginmatchread_char()with|None->(* end of input *)add_line()|Somec->incrcur;matchcwith|'\r'->loop()|'\n'->add_line();loop()|_->Buffer.add_charbc;loop()endinloop();List.rev!lines(* Attempt to get lines from the lexing buffer. *)letlines_around_from_lexbuf~(start_pos:position)~(end_pos:position)(lb:lexbuf):input_linelist=(* Converts a global position to one that is relative to the lexing buffer *)letreln=n-lb.lex_abs_posinifrelstart_pos.pos_bol<0thenbegin(* Do nothing if the buffer does not contain the input (because it has been
refilled while lexing it) *)[]endelsebeginletpos=ref0in(* relative position *)letseekn=pos:=relninletread_char()=if!pos>=lb.lex_buffer_lenthen(* end of buffer *)Noneelseletc=Bytes.getlb.lex_buffer!posinincrpos;Somecinlines_around~start_pos~end_pos~seek~read_charend(* Attempt to get lines from the phrase buffer *)letlines_around_from_phrasebuf~(start_pos:position)~(end_pos:position)(pb:Buffer.t):input_linelist=letpos=ref0inletseekn=pos:=ninletread_char()=if!pos>=Buffer.lengthpbthenNoneelsebeginletc=Buffer.nthpb!posinincrpos;Somecendinlines_around~start_pos~end_pos~seek~read_char(* A [get_lines] function for [highlight_quote] that reads from the current
input. *)letlines_around_from_current_input~start_pos~end_pos=match!input_lexbuf,!input_phrase_buffer,!input_namewith|_,Somepb,"//toplevel//"->lines_around_from_phrasebufpb~start_pos~end_pos|Somelb,_,_->lines_around_from_lexbuflb~start_pos~end_pos|None,_,_->[](******************************************************************************)(* Reporting errors and warnings *)typemsg=Fmt.tlocletmsg?(loc=none)fmt=Fmt.kdoc_printf(funtxt->{loc;txt})fmttypereport_kind=|Report_error|Report_warningofstring|Report_warning_as_errorofstring|Report_alertofstring|Report_alert_as_errorofstringtypereport={kind:report_kind;main:msg;sub:msglist;footnote:Fmt.toption;}typereport_printer={(* The entry point *)pp:report_printer->Format.formatter->report->unit;pp_report_kind:report_printer->report->Format.formatter->report_kind->unit;pp_main_loc:report_printer->report->Format.formatter->t->unit;pp_main_txt:report_printer->report->Format.formatter->Fmt.t->unit;pp_submsgs:report_printer->report->Format.formatter->msglist->unit;pp_submsg:report_printer->report->Format.formatter->msg->unit;pp_submsg_loc:report_printer->report->Format.formatter->t->unit;pp_submsg_txt:report_printer->report->Format.formatter->Fmt.t->unit;}letis_dummy_locloc=(* Fixme: this should be just [loc.loc_ghost] and the function should be
inlined below. However, currently, the compiler emits in some places ghost
locations with valid ranges that should still be printed. These locations
should be made non-ghost -- in the meantime we just check if the ranges are
valid. *)loc.loc_start.pos_cnum=-1||loc.loc_end.pos_cnum=-1(* It only makes sense to highlight (i.e. quote or underline the corresponding
source code) locations that originate from the current input.
As of now, this should only happen in the following cases:
- if dummy locs or ghost locs leak out of the compiler or a buggy ppx;
- more generally, if some code uses the compiler-libs API and feeds it
locations that do not match the current values of [!Location.input_name],
[!Location.input_lexbuf];
- when calling the compiler on a .ml file that contains lexer line directives
indicating an other file. This should happen relatively rarely in practice --
in particular this is not what happens when using -pp or -ppx or a ppx
driver.
*)letis_quotable_locloc=not(is_dummy_locloc)&&loc.loc_start.pos_fname=!input_name&&loc.loc_end.pos_fname=!input_nameleterror_style()=match!Clflags.error_stylewith|Somesetting->setting|None->Misc.Error_style.default_settingletbatch_mode_printer:report_printer=letpp_loc_selfreportppfloc=lettag=matchreport.kindwith|Report_warning_as_error_|Report_alert_as_error_|Report_error->"error"|Report_warning_|Report_alert_->"warning"inlethighlightppfloc=matcherror_style()with|Misc.Error_style.Contextual->ifis_quotable_loclocthenhighlight_quoteppf~get_lines:lines_around_from_current_inputtag[loc]|Misc.Error_style.Short->()inFormat.fprintfppf"@[<v>%a:@ %a@]"print_locloc(Fmt.compathighlight)locinletpp_txtppftxt=Format.fprintfppf"@[%a@]"Fmt.Doc.formattxtinletpp_footnoteppff=Option.iter(Format.fprintfppf"@,%a"pp_txt)finletppselfppfreport=setup_tags();separate_new_messageppf;(* Make sure we keep [num_loc_lines] updated.
The tabulation box is here to give submessage the option
to be aligned with the main message box
*)print_updating_num_loc_linesppf(funppf()->Format.fprintfppf"@[<v>%a%a%a: %a%a%a%a%a@]@."Format.pp_open_tbox()(self.pp_main_locselfreport)report.main.loc(self.pp_report_kindselfreport)report.kindFormat.pp_set_tab()(self.pp_main_txtselfreport)report.main.txt(self.pp_submsgsselfreport)report.subpp_footnotereport.footnoteFormat.pp_close_tbox())()inletpp_report_kind_self_ppf=function|Report_error->Format.fprintfppf"@{<error>Error@}"|Report_warningw->Format.fprintfppf"@{<warning>Warning@} %s"w|Report_warning_as_errorw->Format.fprintfppf"@{<error>Error@} (warning %s)"w|Report_alertw->Format.fprintfppf"@{<warning>Alert@} %s"w|Report_alert_as_errorw->Format.fprintfppf"@{<error>Error@} (alert %s)"winletpp_main_locselfreportppfloc=pp_locselfreportppflocinletpp_main_txt_self_ppftxt=pp_txtppftxtinletpp_submsgsselfreportppfmsgs=List.iter(funmsg->Format.fprintfppf"@,%a"(self.pp_submsgselfreport)msg)msgsinletpp_submsgselfreportppf{loc;txt}=Format.fprintfppf"@[%a %a@]"(self.pp_submsg_locselfreport)loc(self.pp_submsg_txtselfreport)txtinletpp_submsg_locselfreportppfloc=ifnotloc.loc_ghostthenpp_locselfreportppflocinletpp_submsg_txt_self_ppfloc=pp_txtppflocin{pp;pp_report_kind;pp_main_loc;pp_main_txt;pp_submsgs;pp_submsg;pp_submsg_loc;pp_submsg_txt}letterminfo_toplevel_printer(lb:lexbuf):report_printer=letppselfppferr=setup_tags();(* Highlight all toplevel locations of the report, instead of displaying
the main location. Do it now instead of in [pp_main_loc], to avoid
messing with Format boxes. *)letsub_locs=List.map(fun{loc;_}->loc)err.subinletall_locs=err.main.loc::sub_locsinletlocs_highlighted=List.filteris_quotable_locall_locsinhighlight_terminfolbppflocs_highlighted;batch_mode_printer.ppselfppferrinletpp_main_loc____=()inletpp_submsg_loc__ppfloc=ifnotloc.loc_ghostthenFormat.fprintfppf"%a:@ "print_loclocin{batch_mode_printerwithpp;pp_main_loc;pp_submsg_loc}letbest_toplevel_printer()=setup_terminal();match!status,!input_lexbufwith|Terminfo.Good_term,Somelb->terminfo_toplevel_printerlb|_,_->batch_mode_printer(* Creates a printer for the current input *)letdefault_report_printer():report_printer=if!input_name="//toplevel//"thenbest_toplevel_printer()elsebatch_mode_printerletreport_printer=refdefault_report_printerletprint_reportppfreport=letprinter=!report_printer()inprinter.ppprinterppfreport(******************************************************************************)(* Reporting errors *)typeerror=reporttypedelayed_msg=unit->Fmt.toptionletreport_errorppferr=print_reportppferrletmkerrorlocsubfootnotetxt={kind=Report_error;main={loc;txt};sub;footnote=footnote()}leterrorf?(loc=none)?(sub=[])?(footnote=Fun.constNone)=Fmt.kdoc_printf(mkerrorlocsubfootnote)leterror?(loc=none)?(sub=[])?(footnote=Fun.constNone)msg_str=mkerrorlocsubfootnoteFmt.Doc.(stringmsg_strempty)leterror_of_printer?(loc=none)?(sub=[])?(footnote=Fun.constNone)ppx=mkerrorlocsubfootnote(Fmt.doc_printf"%a"ppx)leterror_of_printer_fileprintx=error_of_printer~loc:(in_file!input_name)printx(******************************************************************************)(* Reporting warnings: generating a report from a warning number using the
information in [Warnings] + convenience functions. *)letdefault_warning_alert_reporterreportmk(loc:t)w:reportoption=matchreportwwith|`Inactive->None|`Active{Warnings.id;message;is_error;sub_locs}->letmsg_of_strstr=Format_doc.Doc.(empty|>stringstr)inletkind=mkis_erroridinletmain={loc;txt=msg_of_strmessage}inletsub=List.map(fun(loc,sub_message)->{loc;txt=msg_of_strsub_message})sub_locsinSome{kind;main;sub;footnote=None}letdefault_warning_reporter=default_warning_alert_reporterWarnings.report(funis_errorid->ifis_errorthenReport_warning_as_erroridelseReport_warningid)letwarning_reporter=refdefault_warning_reporterletreport_warninglocw=!warning_reporterlocwletformatter_for_warnings=refFormat.err_formatterletprint_warninglocppfw=matchreport_warninglocwwith|None->()|Somereport->print_reportppfreportletprerr_warninglocw=print_warningloc!formatter_for_warningswletdefault_alert_reporter=default_warning_alert_reporterWarnings.report_alert(funis_errorid->ifis_errorthenReport_alert_as_erroridelseReport_alertid)letalert_reporter=refdefault_alert_reporterletreport_alertlocw=!alert_reporterlocwletprint_alertlocppfw=matchreport_alertlocwwith|None->()|Somereport->print_reportppfreportletprerr_alertlocw=print_alertloc!formatter_for_warningswletalert?(def=none)?(use=none)~kindlocmessage=prerr_alertloc{Warnings.kind;message;def;use}letdeprecated?def?uselocmessage=alert?def?use~kind:"deprecated"locmessagemoduleStyle=Misc.Styleletauto_include_alertlib=letmessage=Fmt.asprintf"\
OCaml's lib directory layout changed in 5.0. The %a subdirectory has been \
automatically added to the search path, but you should add %a to the \
command-line to silence this alert (e.g. by adding %a to the list of \
libraries in your dune file, or adding %a to your %a file for \
ocamlbuild, or using %a for ocamlfind)."Style.inline_codelibStyle.inline_code("-I +"^lib)Style.inline_codelibStyle.inline_code("use_"^lib)Style.inline_code"_tags"Style.inline_code("-package "^lib)inletalert={Warnings.kind="ocaml_deprecated_auto_include";use=none;def=none;message=Format.asprintf"@[@\n%a@]"Format.pp_print_textmessage}inprerr_alertnonealertletdeprecated_script_alertprogram=letmessage=Fmt.asprintf"\
Running %a where the first argument is an implicit basename with no \
extension (e.g. %a) is deprecated. Either rename the script \
(%a) or qualify the basename (%a)"Style.inline_codeprogramStyle.inline_code(program^" script-file")Style.inline_code(program^" script-file.ml")Style.inline_code(program^" ./script-file")inletalert={Warnings.kind="ocaml_deprecated_cli";use=none;def=none;message=Format.asprintf"@[@\n%a@]"Format.pp_print_textmessage}inprerr_alertnonealert(******************************************************************************)(* Reporting errors on exceptions *)leterror_of_exn:(exn->erroroption)listref=ref[]letregister_error_of_exnf=error_of_exn:=f::!error_of_exnexceptionAlready_displayed_error=Warnings.Errorsleterror_of_exnexn=matchexnwith|Already_displayed_error->Some`Already_displayed|_->letrecloop=function|[]->None|f::rest->matchfexnwith|Someerror->Some(`Okerror)|None->looprestinloop!error_of_exnlet()=register_error_of_exn(function|Sys_errormsg->Some(errorf~loc:(in_file!input_name)"I/O error: %s"msg)|_->None)externalreraise:exn->'a="%reraise"letreport_exceptionppfexn=letrecloopnexn=matcherror_of_exnexnwith|None->reraiseexn|Some`Already_displayed->()|Some(`Okerr)->report_errorppferr|exceptionexnwhenn>0->loop(n-1)exninloop5exnexceptionErroroferrorlet()=register_error_of_exn(function|Errore->Somee|_->None)letraise_errorf?(loc=none)?(sub=[])?(footnote=Fun.constNone)=Fmt.kdoc_printf(funtxt->raise(Error(mkerrorlocsubfootnotetxt)))