123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734moduleResult=Biocaml_resultopenCFStreamopenOr_errorletcheckbmsg=ifbthenOk()elseerror_stringmsgletcheckfbformat=Printf.ksprintf(checkb)formatletcheck_buf~buf~pos~len=check(Bytes.lengthbuf>=pos+len)"Buffer too short"(* Helper functions to get parts of an Int32.t as an int *)letget_8_0=letmask=Int32.of_int_exn0xffinfunx->Int32.bit_andmaskx|>Int32.to_int_exnletget_16_0=letmask=Int32.of_int_exn0xffffinfunx->Int32.bit_andmaskx|>Int32.to_int_exnletget_16_8x=get_8_0(Int32.shift_rightx8)letget_32_4x=Int32.shift_rightx4|>Int32.to_int_exnletget_4_0=letmask=Int32.of_int_exn0xfinfunx->Int32.bit_andmaskx|>Int32.to_int_exn(* NB: uint32 is unpacked as an int64 *)letunpack_unsigned_32_little_endian~buf~pos=letb1=Char.to_intbuf.[pos+0]|>Int64.of_int_exninletb2=Char.to_intbuf.[pos+1]inletb3=Char.to_intbuf.[pos+2]inletb4=Char.to_intbuf.[pos+3]inInt64.bit_or(Int64.shift_leftb124)(Int64.of_int_exn(b2lorb3lorb4))(* This function is adapted from Core.Binary_packing.pack_signed_64_little_endian *)letpack_u32_in_int64_little_endian~buf~posv=(* Safely set the first and last bytes, so that we verify the string bounds. *)Bytes.setbufpos(Char.unsafe_of_intInt64.(to_int_exn(bit_and0xFFLv)));Bytes.setbuf(pos+3)(Char.unsafe_of_intInt64.(to_int_exn(bit_and0xFFL(shift_right_logicalv24))));(* Now we can use [unsafe_set] for the intermediate bytes. *)Bytes.unsafe_setbuf(pos+1)(Char.unsafe_of_intInt64.(to_int_exn(bit_and0xFFL(shift_right_logicalv8))));Bytes.unsafe_setbuf(pos+2)(Char.unsafe_of_intInt64.(to_int_exn(bit_and0xFFL(shift_right_logicalv16))))letint64_is_negn=Int64.(bit_and0x8000000000000000Ln<>zero)letint64_fits_u32n=Int64.(bit_and0xFFFFFFFF00000000Ln=zero)letint64_fits_u31n=Int64.(bit_and0xFFFFFFFF80000000Ln=zero)letint64_fits_s32n=ifint64_is_negnthenint64_fits_u31(Int64.bit_notn)elseint64_fits_u31n(*
A List.init with possibly failing initializer
val result_list_init : int -> f:(int -> ('a, 'b) result) -> ('a list, 'b) result
*)letresult_list_initn~f=letrecauxiaccu=ifi<0thenOkaccuelse(matchfiwith|Oky->aux(i-1)(y::accu)|Error_ase->e)inaux(n-1)[]moduleHeader=struct(* This type definition is essentially identical to the Sam version
but makes parsing faster because in Bam, the seq name of a read
is stored as an int index in the ref_seq array. *)typet={ref_seq:Sam.ref_seqarray;sam_header:Sam.header;}letto_samh=h.sam_headerletof_samsam_header={ref_seq=Array.of_listsam_header.Sam.ref_seqs;sam_header}endopenHeadertypealignment=Sam.alignmentmoduleAlignment0=structtypet={ref_id:int;pos:int;bin_mq_nl:int32;flag_nc:int32;next_ref_id:int;pnext:int;tlen:int;read_name:string;cigar:string;seq:string;(* compressed representation *)qual:string;optional:string;}[@@derivingsexp](* ============================ *)(* ==== ACCESSOR FUNCTIONS ==== *)(* ============================ *)(* option constructor for encodings where a special value of the
input type (here it is [none]) plays the role of [None] *)letoption~nonex=ifPoly.(x=none)thenNoneelseSomexletref_idal=option~none:(-1)al.ref_idletqnameal=option~none:"*"al.read_name(* default is indicated in note 1 of page 14 of the spec *)letflagsal=Int32.shift_rightal.flag_nc16|>Int32.to_int_exn(* because we are shifting right just before, Int32.to_int_exn cannot fail *)|>Sam.Flags.of_intletrnamealheader=tryOk(Option.map(ref_idal)~f:(funid->(Array.getheader.ref_seqid).Sam.name))with_->error_string"Bam.Alignment0.rname: unknown ref_id"letposal=option~none:(-1)al.posletmapqal=option~none:255(get_16_8al.bin_mq_nl)letcigar_op_of_s32x:Sam.cigar_opOr_error.t=letopenSaminletop_len=get_32_4xinmatchget_4_0xwith|0->cigar_op_alignment_matchop_len|1->cigar_op_insertionop_len|2->cigar_op_deletionop_len|3->cigar_op_skippedop_len|4->cigar_op_soft_clippingop_len|5->cigar_op_hard_clippingop_len|6->cigar_op_paddingop_len|7->cigar_op_seq_matchop_len|8->cigar_op_seq_mismatchop_len|_->assertfalseletcigaral=result_list_init(String.lengthal.cigar/4)~f:(funi->lets32=Binary_packing.unpack_signed_32~byte_order:`Little_endian~buf:al.cigar~pos:(i*4)incigar_op_of_s32s32)letrnextalheader=matchal.next_ref_idwith|-1->returnNone|i->Sam.parse_rnextheader.ref_seq.(i).Sam.nameletpnextal=option~none:(-1)al.pnext(* value for unavailable is the corresponding value in SAM - 1 *)lettlenal=option~none:0al.tlenletl_seqal=String.lengthal.qualletchar_of_seq_code=function|0->'='|1->'A'|2->'C'|3->'M'|4->'G'|5->'R'|6->'S'|7->'V'|8->'T'|9->'W'|10->'Y'|11->'H'|12->'K'|13->'D'|14->'B'|15->'N'|l->failwithf"letter not in [0, 15]: %d"l()letseqal=letn=String.lengthal.seqinifn=0thenNoneelseletl_seq=l_seqalinletr=String.makel_seq' 'infori=0ton-1doletc=int_of_charal.seq.[i]inBytes.setr(2*i)(char_of_seq_code(clsr4));if2*i+1<l_seqthenBytes.setr(2*i+1)(char_of_seq_code(cland0xf));done;Somerletqualal=letshift=String.map~f:Char.(func->of_int_exn(to_intc+33))inmatchshiftal.qualwith|qual33->Sam.parse_qualqual33|exceptionFailure_->Or_error.error"Bam.Alignement0.qual: incorrect quality score"al.qualsexp_of_string(** Extracts string in buf starting from pos and finishing with a
NULL character, and returns the position just after it. Returns
an error if no NULL character is encountered before the end of
the string. *)letparse_cstringbufpos=letrecauxi=ifi<String.lengthbufthenifChar.(buf.[i]='\000')thenreturnielseaux(i+1)elseerror_string"Unfinished NULL terminated string"inauxpos>>=funpos'->return(String.subbuf~pos~len:(pos'-pos),pos'+1)letcCsSiIf_size=function|'c'|'C'->return1|'s'|'S'->return2|'i'|'I'|'f'->return4|_->error_string"Incorrect numeric optional field type identifier"letparse_cCsSiIfbufpostyp=cCsSiIf_sizetyp>>=funlen->check_buf~buf~pos~len>>=fun()->matchtypwith|'c'->leti=Int64.of_int_exn(Binary_packing.unpack_signed_8~buf~pos)inreturn(Sam.optional_field_value_ii,len)|'C'->leti=Int64.of_int_exn(Binary_packing.unpack_unsigned_8~buf~pos)inreturn(Sam.optional_field_value_ii,len)|'s'->leti=Int64.of_int_exn(Binary_packing.unpack_signed_16_little_endian~buf~pos)inreturn(Sam.optional_field_value_ii,len)|'S'->leti=Int64.of_int_exn(Binary_packing.unpack_unsigned_16_little_endian~buf~pos)inreturn(Sam.optional_field_value_ii,len)|'i'->leti=Binary_packing.unpack_signed_32~byte_order:`Little_endian~buf~posinreturn(Sam.optional_field_value_i(Int64.of_int32i),len)|'I'->leti=unpack_unsigned_32_little_endian~buf~posinreturn(Sam.optional_field_value_ii,len)|'f'->letf=Binary_packing.unpack_float~byte_order:`Little_endian~buf~posinreturn(Sam.optional_field_value_ff,len)|_->error_string"Incorrect numeric optional field type identifier"letparse_optional_field_valuebufpos=function|'A'->check_buf~buf~pos~len:1>>=fun()->Sam.optional_field_value_Abuf.[pos]>>=funv->return(v,1)|'c'|'C'|'s'|'S'|'i'|'I'|'f'astyp->parse_cCsSiIfbufpostyp|'Z'->parse_cstringbufpos>>=fun(s,pos')->Sam.optional_field_value_Zs>>=funvalue->return(value,pos'-pos)|'H'->parse_cstringbufpos>>=fun(s,pos')->Sam.optional_field_value_Hs>>=funvalue->return(value,pos'-pos)|'B'->check_buf~buf~pos~len:5>>=fun()->lettyp=buf.[0]inletn=Binary_packing.unpack_signed_32~buf~pos:(pos+1)~byte_order:`Little_endianin(matchInt32.to_intnwith|Somen->cCsSiIf_sizetyp>>=funelt_size->check_buf~buf~pos:(pos+5)~len:(n*elt_size)>>=fun()->letelts=List.initn~f:(funi->String.subbuf~pos:(pos+5+i*elt_size)~len:elt_size)inletbytes_read=5(* array type and size *)+elt_size*ninSam.optional_field_value_Btypelts>>=funvalue->return(value,bytes_read)|None->error_string"Too many elements in B-type optional field")|c->error"Incorrect optional field type identifier"c[%sexp_of:char]letparse_optional_fieldbufpos=check_buf~buf~pos~len:3>>=fun()->lettag=String.subbuf~pos~len:2inletfield_type=buf.[pos+2]inparse_optional_field_valuebuf(pos+3)field_type>>=fun(field_value,shift)->Sam.optional_fieldtagfield_value>>=funfield->return(field,shift+3)letparse_optional_fieldsbuf=letrecloopbufposaccu=ifpos=String.lengthbufthenreturn(List.revaccu)elseparse_optional_fieldbufpos>>=fun(field,used_chars)->loopbuf(pos+used_chars)(field::accu)inloopbuf0[]letoptional_fieldsal=tag(parse_optional_fieldsal.optional)~tag:"Bam.Alignment0.optional_fields"(* ============================ *)(* ==== ALIGNMENT DECODING ==== *)(* ============================ *)(* Alignement0.t -> Alignment.t conversion *)letdecodealheader=flagsal>>=funflags->rnamealheader>>=funrname->cigaral>>=funcigar->rnextalheader>>=funrnext->qualal>>=funqual->optional_fieldsal>>=funoptional_fields->Sam.alignment?qname:(qnameal)~flags?rname?pos:(posal)?mapq:(mapqal)~cigar?rnext?pnext:(pnextal)?tlen:(tlenal)?seq:(seqal)~qual~optional_fields()(* ============================ *)(* ==== ALIGNMENT ENCODING ==== *)(* ============================ *)(* Alignment.t -> Alignment0.t conversion *)letfind_ref_idheaderref_name=letopenOr_errorinmatchArray.findiheader.ref_seq~f:(fun_rs->String.(rs.Sam.name=ref_name))with|Some(i,_)->Oki|None->error_string"Bam: unknown reference id"letstring_of_cigar_opscigar_ops=letbuf=Bytes.create(List.lengthcigar_ops*4)inletwriteithi32=letpos=ith*4inBinary_packing.pack_signed_32~byte_order:`Little_endian~buf~posi32inletopenInt32inList.itericigar_ops~f:(funidxop->let_,i=matchopwith|`Alignment_matchi->0l,i|`Insertioni->1l,i|`Deletioni->2l,i|`Skippedi->3l,i|`Soft_clippingi->4l,i|`Hard_clippingi->5l,i|`Paddingi->6l,i|`Seq_matchi->7l,i|`Seq_mismatchi->8l,iinwriteidx(bit_or0l(of_int_exnStdlib.(ilsl4))));bufletsizeofal=letl_seq=l_seqalin8*4+String.lengthal.read_name+1(* NULL terminated string *)+String.lengthal.cigar+(l_seq+1)/2+l_seq+String.lengthal.optional(* supposes interval closed at both ends *)letreg2binsted=matchst,edwith|b,ewhenblsr14=elsr14->((1lsl15)-1)/7+(stlsr14)|b,ewhenblsr17=elsr17->((1lsl12)-1)/7+(stlsr17)|b,ewhenblsr20=elsr20->((1lsl9)-1)/7+(stlsr20)|b,ewhenblsr23=elsr23->((1lsl6)-1)/7+(stlsr23)|b,ewhenblsr26=elsr26->((1lsl3)-1)/7+(stlsr26)|_->0letstring_of_optional_fieldsopt_fields=letfield_value_encoding=function|`B(typ,xs)->Ok('B',sprintf"%c%s"typ(String.concat~sep:""xs))|`Ac->Ok('A',Char.to_stringc)|`ff->letbits=Int32.bits_of_floatfinletbuf=Bytes.create4inBinary_packing.pack_signed_32~byte_order:`Little_endianbits~buf~pos:0;Ok('f',buf)|`ii->(* FIXME: encode i to the smallest usable integer type *)letbuf=Bytes.create4inifint64_fits_u32ithen(pack_u32_in_int64_little_endian~buf~pos:0i;Ok('I',buf))elseifint64_fits_s32ithen(Binary_packing.pack_signed_32~buf~byte_order:`Little_endian~pos:0(Int32.of_int64_exni);Ok('i',buf))elseerror"Sam integer cannot be encoded in BAM format"iInt64.sexp_of_t|`Hs->(letr=ref[]inString.iters~f:(func->r:=sprintf"%02x"(Char.to_intc)::!r);Ok('H',String.concat~sep:""(List.rev!r)^"\000"))|`Zs->Ok('Z',s^"\000")inletopenOr_error.Monad_infixinList.mapopt_fields~f:(funopt_field->field_value_encodingopt_field.Sam.value>>=fun(c,s)->Ok(sprintf"%s%c%s"opt_field.Sam.tagcs))|>Or_error.all>>|String.concat~sep:""letint32i~ubvar=ifi<ubthenmatchInt32.of_intiwith|Somei->returni|None->error_string"invalid conversion to int32"elseerrorf"invalid conversion to int32 (%s than %d)"varubletencode_bin_mq_nl~bin~mapq~l_read_name=letopenInt32inint32bin~ub:65536"bin">>=funbin->int32mapq~ub:256"mapq">>=funmapq->int32l_read_name~ub:256"l_read_name">>=funl_read_name->return(bit_or(shift_leftbin16)(bit_or(shift_leftmapq8)l_read_name))letencode_flag_nc~flags~n_cigar_ops=letopenInt32inint32flags~ub:65536"flags">>=funflags->int32n_cigar_ops~ub:65536"n_cigar_ops">>=funn_cigar_ops->return(bit_or(shift_leftflags16)n_cigar_ops)letencodealheader=beginmatchal.Sam.rnamewith|Somername->find_ref_idheaderrname|None->Ok(-1)end>>=funref_id->letread_name=Option.value~default:"*"al.Sam.qnameinletseq=Option.value~default:"*"al.Sam.seqinletpos=(Option.value~default:0al.Sam.pos)-1inletbin=reg2binpos(pos+String.(lengthseq))inletmapq=Option.value~default:255al.Sam.mapqinletl_read_name=String.lengthread_name+1in(* NULL terminated string *)encode_bin_mq_nl~bin~mapq~l_read_name>>=funbin_mq_nl->letflags=(al.Sam.flags:>int)inletn_cigar_ops=List.lengthal.Sam.cigarinencode_flag_nc~flags~n_cigar_ops>>=funflag_nc->beginmatchal.Sam.rnextwith|Some`Equal_to_RNAME->Okref_id|Some(`Values)->find_ref_idheaders|None->Ok(-1)end>>=funnext_ref_id->letpnext=Option.value~default:0al.Sam.pnext-1inlettlen=Option.value~default:0al.Sam.tleninletcigar=string_of_cigar_opsal.Sam.cigarinResult.List.mapal.Sam.qual~f:(Phred_score.to_char~offset:`Offset33)>>|String.of_char_list>>=funqual->string_of_optional_fieldsal.Sam.optional_fields>>=funoptional->Ok{ref_id;pos;bin_mq_nl;flag_nc;cigar;next_ref_id;pnext;tlen;seq;qual;optional;read_name}endletmagic_string="BAM\001"letinput_s32_as_intiz=Int32.to_int_exn(Bgzf.input_s32iz)letread_sam_headeriz=tryletmagic=Bgzf.input_stringiz4incheckString.(magic=magic_string)"Incorrect magic string, not a BAM file">>=fun()->letl_text=input_s32_as_intizincheck(l_text>=0)"Incorrect size of plain text in BAM header">>=fun()->lettext=Bgzf.input_stringizl_textinSam.parse_headertextwithEnd_of_file->error_string"EOF while reading BAM header"letread_one_reference_informationiz=tryletl_name=input_s32_as_intizincheck(l_name>0)"Incorrect encoding of reference sequence name in BAM header">>=fun()->letname=Bgzf.input_stringiz(l_name-1)inlet(_:char)=Bgzf.input_charizin(* name is a NULL terminated string *)letlength=input_s32_as_intizinSam.ref_seq~name~length()withEnd_of_file->error_string"EOF while reading BAM reference information"letread_reference_informationiz=letrecloopaccun=ifn=0thenOk(List.revaccu)elsematchread_one_reference_informationizwith|Okrefseq->loop(refseq::accu)(n-1)|Error_ase->eintryletn_ref=input_s32_as_intizinloop[]n_ref>>|Array.of_listwithEnd_of_file->error_string"EOF while reading BAM reference information"letread_alignment_auxizblock_size=tryletref_id=input_s32_as_intizinbeginmatchBgzf.input_s32iz|>Int32.to_intwith|Some2147483647(* POS in BAM is 0-based *)|None->error_string"A read has a position greater than 2^31"|Somen->ifn<-1thenerrorf"A read has a negative position %d"nelsereturnnend>>=funpos->letbin_mq_nl=Bgzf.input_s32izinletl_read_name=get_8_0bin_mq_nlincheckf(l_read_name>0)"Alignment with l_read_name = %d"l_read_name>>=fun()->letflag_nc=Bgzf.input_s32izinletn_cigar_ops=get_16_0flag_ncinletl_seq=input_s32_as_intizincheck(l_seq>=0)"Incorrect sequence length in alignment">>=fun()->letnext_ref_id=input_s32_as_intizinbeginmatchBgzf.input_s32iz|>Int32.to_intwith|Some2147483647|None->error_string"A read has a position > than 2^31"|Somen->ifn<-1thenerrorf"A read has a negative next position %d"nelsereturnnend>>=funpnext->lettlen=input_s32_as_intizinletread_name=letr=Bgzf.input_stringiz(l_read_name-1)inlet(_:char)=Bgzf.input_charizin(* trailing null character *)rinletcigar=Bgzf.input_stringiz(n_cigar_ops*4)inletseq=Bgzf.input_stringiz((l_seq+1)/2)inletqual=Bgzf.input_stringizl_seqinletremaining=block_size-32-l_read_name-4*n_cigar_ops-((l_seq+1)/2)-l_seqinletoptional=Bgzf.input_stringizremaininginreturn{Alignment0.ref_id;read_name;flag_nc;pos;bin_mq_nl;cigar;next_ref_id;pnext;tlen;seq;qual;optional}withEnd_of_file->error_string"EOF while reading alignment"letread_alignmentiz=tryletblock_size=input_s32_as_intizinSome(read_alignment_auxizblock_size)withEnd_of_file->Noneletread_alignment_streamiz=Stream.from(fun_->read_alignmentiz)letread_headeriz=read_sam_headeriz>>=funsam_header->read_reference_informationiz>>=funref_seq->Ok{sam_header;ref_seq}letread0ic=letiz=Bgzf.of_in_channelicinread_headeriz>>=funheader->return(header,read_alignment_streamiz)letwith_file0fn~f=In_channel.with_file~binary:truefn~f:(funic->read0ic>>=fun(header,alignments)->fheaderalignments)letwrite_plain_SAM_headerhoz=letopenSaminletbuf=Buffer.create1024inletadd_linex=Buffer.add_stringbufx;Buffer.add_charbuf'\n'inOption.iterh.version~f:(funversion->lethl=header_line~version?sort_order:h.sort_order()|>ok_exnin(* the construction of the header line must be valid since we are building it from a validated header *)add_line(print_header_linehl));List.iterh.ref_seqs~f:(funx->add_line(print_ref_seqx));List.iterh.read_groups~f:(funx->add_line(print_read_groupx));List.iterh.programs~f:(funx->add_line(print_programx));List.iterh.comments~f:(funx->Buffer.add_stringbuf"@CO\t";add_linex);List.iterh.others~f:(funx->add_line(print_otherx));Bgzf.output_s32oz(Int32.of_int_exn(Buffer.lengthbuf));(* safe conversion of int32 to int: SAM headers less than a few KB *)Bgzf.output_stringoz(Buffer.contentsbuf)letoutput_null_terminated_stringozs=Bgzf.output_stringozs;Bgzf.output_charoz'\000'letwrite_reference_sequenceshoz=letopenSaminBgzf.output_s32oz(Int32.of_int_exn(Array.lengthh.ref_seq));(* safe conversion: more than a few million reference sequences cannot happen in practice *)Array.iterh.ref_seq~f:(funrs->Bgzf.output_s32oz(Int32.of_int_exn(String.lengthrs.name+1));(* safe conversion: the length of the name of a reference sequence is shorter than a few hundreds *)output_null_terminated_stringozrs.name;Bgzf.output_s32oz(Int32.of_int_exnrs.length);(* FIXME: the conversion is possibly not safe, but maybe [Sam.ref_seq] type should keep the int32 representation? *))letwrite_headerheaderoz=Bgzf.output_stringozmagic_string;write_plain_SAM_headerheader.sam_headeroz;write_reference_sequencesheaderozletwrite_alignmentozal=letopenAlignment0inBgzf.output_s32oz(Int32.of_int_exn(sizeofal));Bgzf.output_s32oz(Int32.of_int_exnal.ref_id);Bgzf.output_s32oz(Int32.of_int_exnal.pos);Bgzf.output_s32ozal.bin_mq_nl;Bgzf.output_s32ozal.flag_nc;Bgzf.output_s32oz(Int32.of_int_exn(l_seqal));Bgzf.output_s32oz(Int32.of_int_exnal.next_ref_id);Bgzf.output_s32oz(Int32.of_int_exnal.pnext);Bgzf.output_s32oz(Int32.of_int_exnal.tlen);output_null_terminated_stringozal.read_name;Bgzf.output_stringozal.cigar;Bgzf.output_stringozal.seq;Bgzf.output_stringozal.qual;Bgzf.output_stringozal.optionalletwrite0headeralignmentsoc=letoz=Bgzf.of_out_channelocinwrite_headerheaderoz;Stream.iteralignments~f:(write_alignmentoz);Bgzf.dispose_outozletbindfx=Or_error.bindx~fletreadic=read0ic>>=fun(header,xs)->Ok(header,Stream.mapxs~f:(bind(funr->Alignment0.decoderheader)))letwith_filefn~f=with_file0fn~f:(funheaderxs->fheader(Stream.mapxs~f:(bind(funr->Alignment0.decoderheader))))letwritehxsoc=letmoduleM=structexceptionEofError.tendinletxs=Stream.mapxs~f:(funal->matchAlignment0.encodealhwith|Okr->r|Errore->raise(M.Ee))intrywrite0hxsoc;Ok()withM.Ee->Errore