Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file part.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262(*
* 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.
*)modulePart=structtypet={name:string;sep_indent:string;(** Whitespaces before the [@@@part] separator *)body:string;}letv~name ~sep_indent~body={name;sep_indent;body}letname {name;_}=nameletsep_indent{sep_indent;_}=sep_indentletbody{body;_}=bodyend(** Remove empty strings at the beginning of a list *)letrecremove_empty_heads=function|""::tl->remove_empty_headstl|l->llettrim_empty_rev l=remove_empty_heads (List.rev(remove_empty_headsl))moduleParse_parts=structtypepart_meta={sep_indent:string;name:string}typet=|Contentofstring|Compat_attrofpart_meta(* ^^^^ This is for compat with the [[@@@part name]] delimiters *)|Part_beginofpart_meta|Part_endmoduleRegexp=structletmarker=Re.str"$MDX"letspaces =Re.rep1Re.spaceletid=Re.(rep1(alt[alnum;char '_';char'-';char '=']))letws=Re.(repspace)letcmt=letopenReincompile@@seq[group(non_greedy(repany));group ws;str"(*";spaces;marker;spaces;groupid;spaces;str"*)";]letattribute=letopenReincompile@@whole_string@@seq[groupws;str"[@@@";ws;groupid;ws;str"\"";groupid;str"\"";ws;str"]";ws;opt(str";;");ws;]endletparse_attrline=matchRe.exec_optRegexp.attributelinewith|Someg->(letsep_indent=Re.Group.getg1inletname=Re.Group.getg2inletpayload=Re.Group.getg3inmatchnamewith|"part" ->[Compat_attr{sep_indent;name=payload}]|_-> [])|None->[]letparse_cmtline=matchRe.exec_optRegexp.cmtlinewith|Someg->(letsep_indent=Re.Group.getg2inmatchRe.Group.getg3with|"part-end"->letentries=match Re.Group.getg1with|""->[Part_end]|s->[Contents;Part_end]inOkentries|s->(matchAstring.String.cut~sep:"="swith|Some("part-begin",name)->Ok[Part_begin{sep_indent;name}]|Some("part-end",_)->Util.Result.errorf"'part-end' delimiter does not accept a value. Please write \
'(* $MDX part-end *)' instead."|_->Util.Result.errorf"'%s' is not a valid ocaml delimiter for mdx."line))|None->Ok []letparseline=matchparse_attrlinewith|[]->(letopenUtil.Result.Infixinlet*delimiters =parse_cmtlineinmatchdelimiterswith|[]->Ok[Contentline]|delimiters->Okdelimiters)|delimiters->Okdelimitersletnext_part{name;sep_indent}~is_begin_end_part lines_rev=let body=ifis_begin_end_partthenString.concat "\n"(List.revlines_rev)else"\n"^String.concat"\n"(trim_empty_rev lines_rev)inPart.v~name~sep_indent~bodyletanonymous_part =next_part{name="";sep_indent=""}letparse_lineline=matchparselinewith|Okcontent ->content|Error(`Msgmsg)->Fmt.epr"Warning: %s\n"msg;[Contentline]letparsed_input_linei=matchinput_line iwith|exception End_of_file->None|line->Some(parse_lineline)letparsed_seqi=letrecloop seq=matchparsed_input_lineiwith|None->seq|Someinputs->letinputs=List.to_seqinputsinlettail=loopseqinUtil.Seq.appendinputstailinloop Seq.emptyletparse_partsinput=letopen Util.Result.Infixinlet*parts,make_part,current_part,part_lines,lineno=Seq.fold_left(funaccparse_part->let*parts,make_part,current_part,part_lines,lineno=accinletlineno=lineno+1inmatch(parse_part,current_part)with|Contentline,_->Ok(parts,make_part,current_part,line::part_lines,lineno)|Part_end,Some_->letpart=make_part~is_begin_end_part:truepart_linesinOk(part::parts,anonymous_part,None,[],lineno)|Part_end,None->Error("There is no part to end.",lineno)|Part_begin meta,None->letnamed_part=next_partmetainletparts=matchpart_lineswith|[]->(* Ignore empty anonymous parts: needed for legacy support *)parts|_->letpart=make_part~is_begin_end_part:truepart_linesinpart::partsinOk(parts,named_part,Somemeta.name,[],lineno)|Compat_attr meta,None->letnamed_part=next_partmetainletpart=make_part~is_begin_end_part:falsepart_linesinOk(part::parts,named_part,None,[],lineno)|Part_begin _,Somep|Compat_attr_,Somep->letmsg=Printf.sprintf"Part %s has no end."pinError(msg,lineno))(Ok([],anonymous_part,None,[],0))inputinlet*part=matchcurrent_part with|Somepart->letmsg=Printf.sprintf"File ended before part %s ended."partinError(msg,lineno+1)|None->Ok(make_part~is_begin_end_part:truepart_lines)inpart::parts|>List.rev|>Result.oklet of_filename=letchannel=open_innameinletinput =parsed_seqchannel inmatchparse_partsinputwith|Okparts->parts|Error (msg,line)->Fmt.failwith"In file %s, line %d: %s"namelinemsgendtypefile=Part.tlistletreadfile=Parse_parts.of_file fileletfindfile~part=match part with|Somepart ->(matchList.find_opt(funp->String.equal(Part.namep)part)filewith|Somep->Some[Part.bodyp]|None->None)|None->List.fold_left(funaccp->Part.bodyp::acc)[]file|>List.rev|>funx->Somexletrecreplace_or_appendpart_namebody=function|p::tlwhenString.equal(Part.namep)part_name ->{pwithbody}::tl|p::tl->p::replace_or_appendpart_namebodytl|[]->[{name=part_name;sep_indent="";body}]letreplacefile~part~lines=letpart=match partwithNone->""|Somep->pinreplace_or_appendpart(String.concat"\n"lines)fileletcontentsfile =letlines=List.fold_left(funaccp->letbody=Part.bodypinmatchPart.namepwith|""->body::acc|n->letindent=Part.sep_indentpinbody::("\n"^indent^"[@@@part \""^n^"\"] ;;\n")::acc)[]fileinletlines=List.revlinesinletlines =String.concat"\n"linesinString.trimlines^"\n"module Internal =structmoduleParse_parts=Parse_partsend