123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425(* Time-stamp: <modified the 23/08/2019 (at 15:44) by Erwan Jahier> *)(*-----------------------------------------------------------------------
** This file may only be copied under the terms of the CeCill
** Public License
**-----------------------------------------------------------------------
**
** Author: erwan.jahier@univ-grenoble-alpes.fr
**
*)openLocalGenlexletrif_file=ref""letplot_file=ref""letdynamic=reffalseletgrid=reftrueletmin_step=refNoneletmax_step=refNoneletwindow_size=ref40letvars_to_hide=ref[]letvars_to_show=ref[](********************************************************************************)let(readfile:string->string)=funname->letchanin=open_in_binnameinletlen=1024inlets=Bytes.createleninletbuf=Buffer.createleninletreciter()=tryletn=inputchanins0leninifn=0then()else(Buffer.add_subbytesbufs0n;iter())withEnd_of_file->()inletremove_control_mstr=Str.global_replace(Str.regexp"\013")""strinletstr=iter();close_inchanin;Buffer.contentsbufinifstr=""then(output_stringstderr("*** File "^name^" is empty!\n");"")else(remove_control_mstr)(********************************************************************************)(* Well, using reg expr would have been simpler actually... *)letlexer=make_lexer["#";"outputs";"inputs";"columns";":";"|";")";"("]typetok=tokenStream.tletverbose=reffalseletdebug_msgmsg=if!verbosethen(output_stringstdout("\ngnuplot-rif: "^msg);flushstdout)let(_print_debug:string->tok->unit)=funmsgtok->if!verbosethen(output_stringstdout((string_of_int(Stream.counttok))^": "^msg);flushstdout)else()(* for debuging *)lettok2str=function|Kwds->s|Idents->s|Inti->string_of_inti|Floatf->string_of_floatf|Strings->s|Charc->Char.escapedcletftok2strstream=matchStream.peekstreamwith|None->""|Sometok->tok2strtok(********************************************************************************)(* get var type in the rif file *)(* name, type, position (starting from 0) *)typevtypes_tbl=(string*(string*int*bool))listlet(get_var_types:string->vtypes_tbl)=funrif_file->let_=debug_msg"try to find the variable names and types"inlettbl=ref[]inlets_ref=ref(lexer(Stream.of_stringrif_file))in(* recovering stupid errors *)letpos_ref=ref0inletis_input=reftrueinletrecauxposs=(* aux looks for the first "string" *)s_ref:=s;pos_ref:=pos;let_=debug_msg("\t aux: "^(string_of_int(Stream.counts))^" pos="^(string_of_intpos)^" ; token='"^(ftok2strs)^"'")inmatchStream.nextswith|String(id)->aux2posids|Ident(_)->auxposs|Int(_)->auxposs|Float(_)->auxposs|Kwd("inputs")->is_input:=true;auxposs|Kwd("outputs")->is_input:=false;auxposs|Kwd(_)->auxposs|Char(_)->auxpossandaux2posids=(* aux2 looks for the next ":" *)let_=debug_msg("aux2: "^(string_of_int(Stream.counts))^" pos="^(string_of_intpos)^" ; token='"^(ftok2strs)^"'\n")inmatchStream.nextswith|Kwd(":")->aux3posids|Ident(_)->auxposs|Int(_)->auxposs|Float(_)->auxposs|Kwd(_)->auxposs|Char(_)->auxposs|String(_)->auxpossandaux3posids=(* aux3 looks for the next ident *)let_=debug_msg("aux3: "^(string_of_int(Stream.counts))^" pos="^(string_of_intpos)^" ; token='"^(ftok2strs)^"'\n")inmatchStream.nextswith|Ident(t)->tbl:=(id,(t,pos,!is_input))::!tbl;aux(pos+1)s|Kwd(_)->auxposs|Int(_)->auxposs|Float(_)->auxposs|String(_)->auxposs|Char(_)->auxpossinletrecaux_ignore_errorposs=tryauxposswith|Stream.Error_->if!verbosethen(output_stringstderr("ignore (harmless?) stream errors at pos "^(string_of_int(Stream.counts))^" in gnuplot-rif.\n");flushstderr);aux_ignore_error!pos_ref!s_refintryaux_ignore_error0!s_refwith|Stream.Failure->List.rev!tbl|Stream.Error(msg)->print_string("gnuplot-rif:"^msg^"\n");flushstdout;List.rev!tblletressource_file_usage="
gnuplot-rif first reads the content of a file named .gnuplot-rif in the
current directory, if it exists. If it contains:
hide T
hide toto*
gnuplot-rif will ignore all I/O which names begin by 'toto', as well as
the variable 'T'. If it contains:
show xx*
it will show show only I/O beginning by 'xx'. If it contains:
plot_range 12 42
it will plot data from step 12 to 42 only. If it contains:
dynamic
window_size 56
it will show only the last of 56 steps of the simulation (40 by default).
If one 'show' statement is used, all hide statements are ignored.
If several plot_range or window_size are used, the last one win.
All these values can be overriden by setting options.
"(* Returns the list of var names to hide *)let(read_ressource_file_name:string->unit)=funfile->tryletstr=ifSys.file_existsfilethenreadfilefileelse""inletstrl=Str.split(Str.regexp"\n")strinletstrll=List.map(Str.split(Str.regexp"[ \t]+"))strlin(* XXX I could use a less rustic parsing methodology... *)lethide_strll(* lines beggining by 'hide' *),strll(* other lines *)=List.partition(funl->ifl=[]thenfalseelseList.hdl="hide")strllinletshow_strll(* lines beggining by 'show' *),strll(* other lines *)=List.partition(funl->ifl=[]thenfalseelseList.hdl="show")strllinList.iter(function|["plot_range";min_s;max_s]->(print_string("Plotting from step "^min_s^" to step "^max_s^"\n");min_step:=(trySome(int_of_stringmin_s)with_->None);max_step:=(trySome(int_of_stringmax_s)with_->None))|["window_size";size]->((trywindow_size:=(int_of_stringsize)with_->()))|_->())strll;debug_msg"read .gnuplot-rif: ok.\n";vars_to_hide:=!vars_to_hide@(List.flatten(List.mapList.tlhide_strll));vars_to_show:=!vars_to_show@(List.flatten(List.mapList.tlshow_strll))with_->if!verbosethen(print_string"No valid .gnuplot-rif file has been found.\n";flushstdout)let(read_ressource_file:unit->unit)=fun()->read_ressource_file_name".gnuplot-rif"(********************************************************************************)typeterminal_kind=Jpg|Ps|Pdf|Cps|Eps|Latex|X11|Wxt|Qt|NoDisplay|Defaultletterminal_kind_to_stringtkfile=letbase_fn=Filename.chop_extensionfileinmatchtkwith|Jpg->"set term jpeg \nset output \""^base_fn^".jpg\"\n"|Pdf->"set term pdf \nset output \""^base_fn^".pdf\"\n"|Ps->"set term post solid \nset output \""^base_fn^".ps\"\n"|Cps->"set term post color solid \nset output \""^base_fn^".ps\"\n"|Eps->"set term post color solid eps\nset output \""^base_fn^".eps\"\n"|Latex->"set term latex\nset output \""^base_fn^".tex\"\n"|X11->"set terminal x11"|Qt->"set terminal qt size 1600,400"|Default->""|Wxt->"set terminal wxt persist font \"Arial,12\" size 1600,400 "|NoDisplay->" "letterminal=refDefaultletgen_gnuplot_filevarsto_hidettblfiletk=letoc=open_outfileinletputstr=output_stringocstrin(* let flip = ref true in *)letbool_var_nb=ref0inletput_one_var(var:string)(i:int)=letis_num,is_input=trylett,_,ii=(List.assocvarttbl)in(t<>"bool"),iiwith_->letvar_tbl_str=String.concat", "(List.map(fun(n,_)->n)ttbl)inoutput_stringstderr("Warning: cannot find "^var^" in "^var_tbl_str^"\n");flushstderr;false,false(* fake values *)input("\"< read-rif.sh "^!rif_file^" ");if!dynamicthenput(" | tail -n "^(string_of_int!window_size))else(match!min_step,!max_stepwith|None,None->()|Somel,None->put(" | tail -n +"^(string_of_intl))|None,Someh->put(" | head -n "^(string_of_inth))|Somel,Someh->put(" | head -n "^(string_of_inth)^" | tail -n +"^(string_of_intl)));put("\" using 1:"^(ifis_numthen("(getminmax($"^(string_of_inti)^")) title \""^var^"\" with linespoints")else("(scale_bool($"^(string_of_inti)^","^(incrbool_var_nb;string_of_int(!bool_var_nb-1))^")) lc rgb \""^(ifis_inputthen"blue"else"red")^"\" notitle")))inletbool_nb=List.fold_left(funcpt(id,(t,_,_ii))->ift="bool"&¬(to_hideid)thencpt+1elsecpt)0ttblinletnum_nb=List.fold_left(funcpt(id,(t,_,_ii))->ift<>"bool"&¬(to_hideid)thencpt+1elsecpt)0ttblindebug_msg("Generating "^file^" file...\n");put("
# defaults
set title \"A visualisation of "^(!rif_file)^"\"
set style data steps
set pointsize 0.2");if!gridthenput"
set grid back";put("
set mouse
"^(ifnum_nb=0then""else"set key outside title \"Numeric variables\" box 3")^"
set xlabel \"steps\"
# Set parameters
bool_nb="^(string_of_intbool_nb)^"
# Initialise the global vars
max="^(ifnum_nb=0then"-1"else"-1e36")^"
min="^(ifnum_nb=0then"1"else"1e36")^"
range=0
range_10=1
delta=1
# Various stuff to be able to display booleans
update_delta(x) = (range=max-min,range_10=ceil(range/10),(delta2=((range)/(bool_nb+2)), (delta2<1?1:delta=delta2)))
getminmax(x)= ((x<min ? min=x : x>max ? (max=x) : x),update_delta(x),x)
scale_bool(x,i) = min + 1.7*i*delta + (x*delta)
label_pos(i)=min + i*delta*1.7+delta*0.5
"^(if!dynamicthen("set xtics "^(string_of_int(!window_size/10)))elsematch!min_step,!max_stepwith|None,None->""|Some_l,None->""|None,Someh->"set xtics "^(string_of_int(h/10))|Somel,Someh->"set xtics "^(string_of_int((h-l)/10)))^"\n");put(terminal_kind_to_stringtkfile);put"\nplot ";ignore(List.fold_left(fun(i,sep)var->ifto_hidevarthen(if!verbosethenprint_string("\n Skipping hidden var "^var);(i+1,sep))else(putsep;put_one_varvari;(i+1,", \\\n ")))(2,"")vars);put"\n\nunset label\n";bool_var_nb:=0;List.iter(fun(id,(t,_pos,_ii))->if(to_hideid)thendebug_msg("Hidding "^id^"\n");ift="bool"then(if(to_hideid)then()else(put("set label \""^id^"\" at 3, (label_pos("^(incrbool_var_nb;string_of_int(!bool_var_nb-1))^")) front left \n")));)ttbl;put"
unset ytics
set ytics range_10
";flushstderr;if(not(List.memtk[Wxt;Default;Qt;X11;NoDisplay]))thenput"set size 1.3,1\n";put"unset output\nreplot\n";if(List.memtk[Qt;X11;Default;NoDisplay])thenput"pause mouse close\n";close_outoc;file(********************************************************************************)letgnuplot=tryUnix.getenv"GNUPLOT"with_->"gnuplot"(********************************************************************************)let(f:unit->out_channel*int)=fun()->letrif_file_content=readfile!rif_filein(* XXX I only need the first lines ! *)letttbl=get_var_typesrif_file_contentinletto_hidev=if!vars_to_show=[]thenList.exists(funpatt->letpatt=Str.global_replace(Str.regexp_string"*")".*"pattinStr.string_match(Str.regexppatt)v0)!vars_to_hideelsenot(List.exists(funpatt->letpatt=Str.global_replace(Str.regexp_string"*")".*"pattinStr.string_match(Str.regexppatt)v0)!vars_to_show)inletvars=fst(List.splitttbl)inletgp_file=(Filename.chop_extension!rif_file)^".gp"inlet(pipe_in,pipe_out)=Unix.pipe()inletoc=Unix.out_channel_of_descrpipe_outinletgnuplot_out=open_out"gnuplot.log"inlet_=plot_file:=gen_gnuplot_filevarsto_hidettblgp_file!terminalinletplot_file_content=readfile!plot_fileinif!verbosethen(output_stringstderr"\nvar names are :\n";List.iter(funx->output_stringstderr(x^"\n"))vars;flushstderr);if!terminal=NoDisplaythenoc,0else(letpid=debug_msg("Launching "^gnuplot^" "^!plot_file^"...\n");Unix.create_processgnuplot(Array.of_list[gnuplot;"-"])(* (Array.of_list [gnuplot; !plot_file]) *)(pipe_in)(Unix.descr_of_out_channelgnuplot_out)(Unix.descr_of_out_channelstderr)inoutput_stringocplot_file_content;flushoc;oc,pid)(********************************************************************************)