123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832(*
* BatIO - Abstract input/output
* Copyright (C) 2003 Nicolas Cannasse
* 2008 David Teller (contributor)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version,
* with the special exception on linking described in file LICENSE.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*)includeBatInnerIOexternalnoop:unit->unit="%ignore"externaldefault_close :unit->unit="%ignore"type('a,'b)printer ='boutput->'a->unittype 'af_printer=Format.formatter->'a->unitletpos_ini=letp=ref0in(wrap_in~read:(fun()->letc=readiinincrp;c)~input:(funsspl->letn=inputissplinp:=!p+n;n)~close:noop~underlying:[i],(fun()->!p))letpos_outo=letp=ref0in(wrap_out~write:(func->writeoc;incrp)~output:(funsspl->letn=outputossplinp:=!p+n;n)~close:noop~flush:(fun()->flusho)~underlying:[o],fun()->!p)letprogress_ininpf=wrap_in~read:(fun()->letc=readinpinf();c)~input:(funsil->letr=inputinpsilinf();r)~close:ignore~underlying:[inp]letprogress_outoutf=wrap_out~write:(func->writeoutc;f())~output:(funsil->letr=outputoutsilinf();r)~close:ignore~flush:(fun()->flushout)~underlying:[out](**
{6 Support for enumerations}
*)(*Function inlined here to avoid circular dependencies between [BatIO]
and [ExtString].*)letstring_enums=letl=String.lengthsinletrecmakei=BatEnum.make~next:(fun()->if!i=lthenraiseBatEnum.No_more_elementselseletp=!iinincri;String.unsafe_getsp)~count:(fun()->l-!i)~clone:(fun()->make(ref!i))inmake (ref0)letinput_enume=letpos=ref0increate_in~read:(fun()->matchBatEnum.getewith|None->raiseNo_more_input|Somec->incrpos;c)~input:(funspl->letreclooppl=ifl=0then0elsematchBatEnum.getewith|None->l|Somec->Bytes.unsafe_setspc;loop(p+1)(l-1)inletk=loopplinifk=lthenraiseNo_more_input;l-k)~close:default_closeletoutput_enum()=letb=Buffer.createdefault_buffer_sizeincreate_out~write:(funx->Buffer.add_charbx)~output:(funspl->BatBytesCompat.buffer_add_subbytesbspl;l)~close:(fun()->lets=Buffer.contentsbinstring_enums)~flush:default_close(** [apply_enum f x] applies [f] to [x] and converts exceptions
[No_more_input] and [Input_closed] to [BatEnum.No_more_elements]*)letapply_enumdo_closefx=try fxwith|No_more_input->raiseBatEnum.No_more_elements|Input_closed->do_close:=false;raiseBatEnum.No_more_elements(** [close_at_end input e] returns an enumeration which behaves as [e]
and has the secondary effect of closing [input] once everything has
been read.*)letclose_at_enddo_close(input:input)e=BatEnum.suffix_action(fun()->if!do_closethenclose_ininput)eletmake_enumfinput=letdo_close =reftrueinclose_at_end do_closeinput(BatEnum.from(fun()->apply_enum do_closefinput))letcombine (a,b)=wrap_out~write:(func->writeac;writebc)~output:(funsij->let_=outputasijinoutputbsij)~flush:(fun()->flusha;flushb)~close:(fun()->(close_outa,close_outb))~underlying:[cast_outputa;cast_outputb]letwrite_enumfoutenum=BatEnum.iter(fout)enum(*;
flush out*)(**
{6 Big Endians}
*)moduleBigEndian=structletread_ui16i=letch2=read_byteiinletch1=read_byteiinch1lor(ch2lsl8)letread_i16i=letch2=read_byteiinletch1=read_byteiinletn=ch1lor(ch2lsl8)inifch2land128<>0thenn-65536elsenletfix=lnot0x7FFFFFFF(* -:) *)letread_i32ch=letch4=read_bytechinletch3=read_byte chinletch2=read_byte chinletch1=read_byte chinifch4land128<>0thenbegin(* negative number *)ifch4land64=0thenraise(Overflow"read_i32");(ch1lor(ch2lsl8)lor(ch3lsl16)lor((ch4land127)lsl 24))lorfix(* FIX HERE *)endelsebegin(*positive number*)ifch4land64<>0thenraise(Overflow"read_i32");ch1lor(ch2lsl8)lor(ch3lsl16)lor(ch4lsl24)endletread_real_i32ch=letbig=Int32.shift_left(Int32.of_int(read_bytech))24inletch3=read_bytechinletch2=read_byte chinletch1=read_byte chinletbase=Int32.of_int(ch1 lor(ch2lsl8)lor(ch3lsl16))inInt32.logorbasebigletread_i64ch=letbig=Int64.of_int32(read_real_i32ch)inletch4=read_bytechinletch3=read_byte chinletch2=read_byte chinletch1=read_byte chinletbase=Int64.of_int(ch1 lor(ch2lsl8)lor(ch3lsl16))inletsmall =Int64.logorbase(Int64.shift_left(Int64.of_intch4)24)inInt64.logor(Int64.shift_leftbig32)smallletread_doublech=Int64.float_of_bits(read_i64ch)let read_floatch=Int32.float_of_bits(read_real_i32ch)letwrite_ui16chn=ifn<0||n>0xFFFFthenraise(Overflow"write_ui16");write_bytech(nlsr8);write_bytechnletwrite_i16chn=ifn<-0x8000||n>0x7FFFthenraise(Overflow"write_i16");ifn<0thenwrite_ui16ch(65536+n)elsewrite_ui16chnletwrite_i32chn=write_bytech(nasr24);write_bytech(nlsr16);write_bytech(nlsr8);write_bytechnletwrite_real_i32chn=letbase=Int32.to_intninletbig=Int32.to_int(Int32.shift_right_logical n24)inwrite_byte chbig;write_bytech(baselsr16);write_bytech(baselsr8);write_bytechbaseletwrite_i64chn=write_real_i32 ch(Int64.to_int32(Int64.shift_right_logical n32));write_real_i32 ch(Int64.to_int32n)letwrite_double chf=write_i64 ch(Int64.bits_of_floatf)letwrite_float chf=write_real_i32 ch(Int32.bits_of_floatf)letui16s_ofinput=make_enum read_ui16 inputleti16s_ofinput =make_enum read_i16 inputleti32s_ofinput =make_enum read_i32 inputletreal_i32s_ofinput=make_enumread_real_i32inputleti64s_ofinput =make_enum read_i64 inputletdoubles_ofinput=make_enumread_double inputletfloats_ofinput=make_enum read_float inputend(**
{6 Bits API}
*)type'abc={ch:'a;mutablenbits:int;mutablebits:int;}typein_bits=input bctypeout_bits=unit outputbcexceptionBits_errorletinput_bitsch={ch=ch;nbits=0;bits=0;}letoutput_bitsch={ch=cast_outputch;nbits=0;bits=0;}letrecread_bitsbn=ifb.nbits>=nthenbeginletc=b.nbits-ninletk=(b.bitsasrc)land((1lsln)-1)inb.nbits<-c;kendelsebeginletk=read_byteb.chinifb.nbits>=24thenbeginifn>=31thenraiseBits_error;letc=8+b.nbits-ninletd=b.bitsland((1lslb.nbits)-1)inletd=(dlsl(8-c))lor(klsr c)inb.bits<-k;b.nbits<-c;dendelsebeginb.bits<-(b.bitslsl8)lork;b.nbits<-b.nbits+8;read_bitsbn;endendletdrop_bitsb=b.nbits<-0letrecwrite_bitsb~nbitsx=letn=nbitsinifn+b.nbits >=32thenbeginifn>31thenraiseBits_error;let n2=32-b.nbits-1inletn3=n-n2inwrite_bitsb~nbits:n2(xasrn3);write_bitsb~nbits:n3(xland((1lsln3)-1));endelsebeginifn<0thenraiseBits_error;if(x<0||x>(1lsln-1))&&n<>31thenraiseBits_error;b.bits<-(b.bitslsln)lorx;b.nbits <-b.nbits+n;whileb.nbits>=8dob.nbits<-b.nbits-8;write_byteb.ch(b.bitsasrb.nbits)doneendletflush_bitsb=ifb.nbits>0thenwrite_bitsb~nbits:(8-b.nbits)0(**
{6 Generic BatIO}
*)classin_channelch=objectmethodinputsposlen=inputchspos lenmethodclose_in()=close_inchendclassout_channelch=objectmethodoutputsposlen=outputchsposlenmethodflush()=flushchmethodclose_out()=ignore(close_outch)endclassin_charsch=objectmethodget()=tryreadchwithNo_more_input->raiseEnd_of_filemethod close_in()=close_inchendclassout_charsch=objectmethodputt=writechtmethodflush()=flushchmethodclose_out()=ignore(close_outch)endletfrom_in_channelch=letcbuf=Bytes.create1inletread()=tryifch#inputcbuf01=0thenraiseSys_blocked_io;Bytes.unsafe_getcbuf0withEnd_of_file ->raiseNo_more_inputinletinputspl=ch#inputsplincreate_in~read~input~close:ch#close_inletfrom_out_channelch=letcbuf=Bytes.create1inletwritec=Bytes.unsafe_set cbuf0c;ifch#outputcbuf01=0thenraiseSys_blocked_io;inletoutputspl=ch#outputsplincreate_out~write~output~flush:ch#flush~close:ch#close_outletfrom_in_charsch=letinputspl=leti=ref0intrywhile!i<ldoBytes.unsafe_sets(p+!i)(ch#get());incridone;lwithEnd_of_filewhen!i>0->!iincreate_in~read:ch#get~input~close:ch#close_inletfrom_out_charsch=letoutputspl=fori=ptop+l-1doch#put(Bytes.unsafe_getsi)done;lincreate_out~write:ch#put~output~flush:ch#flush~close:ch#close_out(**
{6 Enumerations}
*)letbytes_ofinput=make_enumread_byteinputletsigned_bytes_ofinput=make_enumread_signed_byteinputletui16s_ofinput=make_enumread_ui16inputleti16s_ofinput=make_enumread_i16inputleti32s_ofinput=make_enumread_i32inputletreal_i32s_ofinput=make_enumread_real_i32inputleti64s_ofinput=make_enumread_i64inputletdoubles_ofinput=make_enumread_doubleinputletfloats_ofinput=make_enumread_floatinputletstrings_ofinput=make_enumread_stringinputletlines_ofinput=make_enumread_lineinputletchunks_ofninput=make_enum(funic ->nreadicn)input(** The number of chars to read at once *)letbuffer_size=1024(*Arbitrary size.*)(* make a bunch of char enums by reading buffer_size at a time and
concat them all into into one big char enum *)letchars_ofinput=letdo_close =reftrueinclose_at_end do_closeinput(BatEnum.concat(BatEnum.from(fun()->apply_enumdo_close(funsource->string_enum(nreadsourcebuffer_size))input)))letbits_ofinput=letdo_close=reftrueinclose_at_end do_closeinput.ch(BatEnum.from(fun()->apply_enum do_closeread_bitsinput1))(** Buffered lines_of, for performance. Ideas taken from ocaml stdlib *)letlines_of2ic=letbuf=Bytes.createbuffer_sizeinletread_pos=ref0in(* next byte to read *)letend_pos=ref0in(* place to write new data *)letfind_eol()=letrecfind_looppos=ifpos>=!end_posthen!read_pos-poselseifBytes.getbufpos='\n'then 1+pos-!read_pos(*TODO:HANDLE CRLF *)elsefind_loop(pos+1)infind_loop!read_posinletjoin_strings total_lenaccu=letrecloopbufpos=function|[]->()|h::t->letlen=Bytes.lengthhinBytes.blit h0buf(pos-len)len;loop buf(pos-len)tinletbuf=Bytes.createtotal_leninloopbuftotal_lenaccu;Bytes.unsafe_to_string bufinletinput_buf sol=Bytes.blitbuf!read_possol;read_pos:=!read_pos+l;if!end_pos=!read_posthentryif!end_pos>=buffer_sizethenbeginread_pos:=0;end_pos :=inputicbuf0buffer_size;endelsebeginletlen_read=inputicbuf0(buffer_size-!end_pos)inend_pos :=!end_pos+len_read;endwithNo_more_input->end_pos:=!read_pos;inletget_line()=letrecget_piecesacculen=let n=find_eol ()inifn=0thenmatchaccuwith(* EOF *)|[]->close_inic;raiseBatEnum.No_more_elements|_->join_stringslenaccuelseifn>0then(* newlinefound *)letres=Bytes.create(n-1)ininput_buf res0(n-1);input_buf (Bytes.of_string" ")01;(* throw away EOL *)matchaccuwith|[]->Bytes.unsafe_to_stringres|_->letlen=len+n-1injoin_stringslen(res::accu)else(* n < 0 ; no newline found *)letpiece=Bytes.create(-n)ininput_bufpiece0(-n);get_pieces (piece::accu)(len-n)inget_pieces[]0in(* prime the buffer *)end_pos:=inputicbuf0buffer_size;BatEnum.fromget_lineletwrite_bitss~nbitsoutputenum=write_enum (write_bits~nbits)outputenum(**
{6 Utilities}
*)letis_newline=function'\010'|'\013'->true|_->falselettab_out?(tab=' ')nout=letspaces=String.makentabinwrap_out~write:(func->writeoutc;ifis_newlinecthennwriteoutspaces;)~output:(funspl->(*Replace each newline within the segment with newline^spaces*)letlength=Bytes.lengthsinletbuffer =Buffer.createlengthinfori=ptomin(length-1)ldoletc=Bytes.unsafe_getsiinBuffer.add_charbufferc;ifis_newlinecthenBuffer.add_stringbufferspacesdone;lets'=BatBytesCompat.buffer_to_bytes bufferinreally_output outs'0(Bytes.lengths'))~flush:noop~close:noop~underlying:[out](*
let lmargin n (p:_ output -> 'a -> unit) out x =
p (tab_out n (cast_output out)) x
*)letcomb(a,b)=create_out~write:(func->writeac;writebc)~output:(funsij->let_=outputasijinoutputbsij)~flush:(fun()->flusha;flushb)~close:(fun()->ignore(close_outa);close_out b)(*let repeat nout =
wrap_out
~underlying:[out]
~write:(fun c -> for i = 1 to n do write out c)
~output:(fun s p l -> for i = 1 to n do output out s p l)
~close:(fun () -> flush out)*)(*let copy input output = write_chunks output (chunks_of default_buffer_size input)*)(*let copy input output = write_chars output (chars_of input)*)letcopy?(buffer=4096)inpout=letn=bufferinletbuf=Bytes.createnintrywhiletruedoletlen=inputinpbuf0niniflen =0thenraiseNo_more_inputelseignore(really_outputoutbuf0len)donewithNo_more_input->()(*let fast_chunks_of n inp =
let buffer = String.create n in
make_enum (fun inp -> input inp buffer 0 n) input*)(*
(** {6 Test} *)
let in_channel_of_input i =
let (cin, cout) = Unix.pipe () in
let latest_pos_in = ref 0 in
let rec aux () =
let new_pos_in = pos_in cin in
if new_pos_in > !latest_pos_in then (*Something has been read, we can write a little bit more*)
let size = new_pos_in - !latest_pos_in in
let buf = String.create size in
input i buf
(* UnixLabels.select
~read:?
~write:*)
(* let (fin, fout) = Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM in
let cin = open_in fin
and cout = open_out fout in
let rec aux () =
let c = read i in
Pervasives.output_char cout c;
aux ()*)
*)(**
{6 Thread-safety}
*)letlock_factory=ref(fun()->BatConcurrent.nolock)letsynchronize_in?(lock=!lock_factory())inp=wrap_in~read:(BatConcurrent.synclock(fun()->read inp))~input:(BatConcurrent.synclock(funspl->inputinpspl))~close:noop~underlying:[inp]letsynchronize_out?(lock=!lock_factory())out=wrap_out~write: (BatConcurrent.synclock(func->writeoutc))~output:(funsp->BatConcurrent.synclock(funl->output out spl))~flush:(BatConcurrent.synclock(fun()->flushout))~close:noop~underlying:[out](**
{6 Things that require temporary files}
*)(**
[to_input_channel inp] converts [inp] to an [in_channel].
In the simplest case, [inp] maps to a file descriptor, which makes
it possible to just reopen the same file descriptor as an
[in_channel]. There is no flushing with which to screw up and
this shouldn't interfere with [pos_in] et al. because [inp]
maps {e directly} to a file descriptor, not through higher-level
abstract streams.
Otherwise, read everything, write it to a temporary file and
read it back as an [in_channel].
Yes, this is prohibitively expensive.
*)letto_input_channelinp=tryletdescr =tryBatUnix.descr_of_inputinpwithInvalid_argument_->raiseExitin(*Simple case*)Unix.in_channel_of_descrdescrwithExit->(*Bad, bad case*)(* FIXME: this 'pipe' is never deleted *)let(name,cout)=Filename.open_temp_file~mode:[Open_binary]"ocaml""pipe"inletout=output_channelcoutincopyinpout;close_outout;open_in_binname(*(**
Copy everything to a temporary file
*)
let out_channel_of_output out =
let (name, cout) = Filename.open_temp_file "ocaml" "tmp" in
create_out
cout*)letto_stringprint_xx=BatPrintf.sprintf2 "%a"print_xxlet to_f_printerprinter=funfmtt->Format.pp_print_stringfmt(to_stringprintert)moduleIncubator=structmoduleArray=structletpp?(flush=false)?(first="[|")?(last="|]")?(sep="; ")?(indent=String.lengthfirst)ppfa=let openFormatinpp_open_boxfindent;pp_print_cutf();pp_print_string ffirst;pp_print_cutf();fori=0toArray.lengtha-2dopp_open_box findent;ppfa.(i);pp_print_string fsep;pp_close_boxf();pp_print_cut f();done;ifArray.lengtha>0then((* Print the last element without a trailing separator *)pp_open_boxfindent;ppfa.(Array.lengtha-1);pp_close_box f(););pp_print_cutf();pp_print_string flast;pp_close_boxf();ifflushthenpp_print_flushf()endmoduleEnum=structletpp?(flush=false)?(first="")?(last="")?(sep=" ")?(indent=String.lengthfirst)ppfe=let openFormatinpp_open_boxfindent;pp_print_cutf();pp_print_string ffirst;pp_print_cutf();matchBatEnum.getewith|None->pp_print_stringflast;pp_close_boxf();ifflush thenpp_print_flushf()|Somex->pp_open_boxfindent;ppfx;letrecaux()=matchBatEnum.getewith|None->pp_close_boxf();pp_print_cutf();pp_print_stringflast;pp_close_boxf();ifflushthenpp_print_flushf()|Some x->pp_print_stringfsep;pp_close_boxf();pp_print_cutf();pp_open_boxfindent;ppfx;aux()inaux()endmoduleList=structletpp?(flush=false)?(first="[")?(last="]")?(sep="; ")?(indent=String.lengthfirst)ppfl=let openFormatinpp_open_boxfindent;pp_print_cutf();pp_print_string ffirst;pp_print_cutf();matchlwith|[]->pp_print_stringflast;pp_close_boxf();ifflush thenpp_print_flushf()|hd::tl->pp_open_box findent;ppfhd;letrecaux rem=match remwith|[]->pp_close_boxf();pp_print_cutf();pp_print_stringflast;pp_close_boxf();ifflushthenpp_print_flushf()|hd::tl->pp_print_stringfsep;pp_close_boxf();pp_print_cutf();pp_open_boxfindent;ppfhd;auxtlinauxtlendend