123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224moduleHt=HashtblmoduleL=Listtypeargs=stringlisttypeoption_strings=stringlist(* options provided by the user on the CLI are called 'raw';
they become 'processed' afterwards *)moduleProcessed_option=structtypet=Sofstring|Cofchar|Iofint|Foffloat|Bofboolletto_string=function|Ss->"S: "^s|Cc->"C: "^(String.make1c)|Ii->"I: "^(string_of_inti)|Ff->"F: "^(string_of_floatf)|Bb->"B: "^(string_of_boolb)endexceptionNot_an_intofstringexceptionNot_a_charofstringexceptionNot_a_stringofstringexceptionNot_a_floatofstringexceptionNot_a_boolofstringmoduleRaw_option=structtypet=Stringofstring|Charofstring|Intofstring|Floatofstring|Boolofstringletto_string=function|Strings|Chars|Ints|Floats|Bools->sletread_floats=tryScanf.sscanfs"%f"(funx->x)with_->raise(Not_a_floats)letread_ints=tryScanf.sscanfs"%d"(funx->x)with_->raise(Not_an_ints)letread_chars=tryScanf.sscanfs"%c"(funx->x)with_->raise(Not_a_chars)letread_bool=function|"on"|"true"->true|"off"|"false"->false|other->raise(Not_a_boolother)letprocessxy=Processed_option.(matchxwith|String_->Sy|Char_->C(read_chary)|Int_->I(read_inty)|Float_->F(read_floaty)|Bool_->B(read_booly))endmoduleState=struct(* store options processed so far *)letoptions_seen=Ht.create11endexceptionNo_param_for_optionofstringletrecget_param(kwd:Raw_option.t)(args:stringlist):Processed_option.t=matchargswith|[]->assert(false)(* case caught in match_kwd *)|curr::rest->letkeyword=Raw_option.to_stringkwdinifcurr<>keywordthenget_paramkwdrestelsematchrestwith|[]->raise(No_param_for_optionkeyword)|value::_->Raw_option.processkwdvalue(* return (argc, argv) *)letinit()=(Array.lengthSys.argv,Array.to_listSys.argv)exceptionMore_than_onceofstringexceptionOption_is_mandatoryofstringexceptionDuplicate_in_specificationofstringletstring_of_stringsl=String.concat", "l(* find if the short or the long option was used on the CLI *)letmatch_kwd(kwd:stringlist)(args:stringlist):string=ifL.lengthkwd>L.length(List.sort_uniqString.comparekwd)thenraise(Duplicate_in_specification(string_of_stringskwd));letmatched=L.filter(funarg->L.exists((=)arg)kwd)argsinmatchmatchedwith|[]->raise(Option_is_mandatory(string_of_stringskwd))|[k]->(Hashtbl.addState.options_seenk();k)|_->raise(More_than_once(string_of_stringskwd))exceptionUnused_optionsofstring(* find if there are unused options left on the CLI.
Note that options start with a '-' *)letfinalize()=letbuff=Buffer.create80inArray.iteri(funiarg->(* i = 0: program/command name *)ifi<>0&&String.getarg0='-'&¬(Hashtbl.memState.options_seenarg)thenbeginifBuffer.lengthbuff>0thenBuffer.add_charbuff',';(* sep *)Buffer.add_stringbuffarg(* unused option *)end)Sys.argv;ifBuffer.lengthbuff>0thenraise(Unused_options(Buffer.contentsbuff))(* mandatory options *)letget_intkwdargs=letk=match_kwdkwdargsinmatchget_param(Raw_option.Intk)argswith|Processed_option.Ii->i|other->raise(Not_an_int(k^" "^(Processed_option.to_stringother)))letget_stringkwdargs=letk=match_kwdkwdargsinmatchget_param(Raw_option.Stringk)argswith|Processed_option.Ss->s|other->raise(Not_a_string(k^" "^(Processed_option.to_stringother)))letget_charkwdargs=letk=match_kwdkwdargsinmatchget_param(Raw_option.Chark)argswith|Processed_option.Cc->c|other->raise(Not_a_char(k^" "^(Processed_option.to_stringother)))letget_floatkwdargs=letk=match_kwdkwdargsinmatchget_param(Raw_option.Floatk)argswith|Processed_option.Ff->f|other->raise(Not_a_float(k^" "^(Processed_option.to_stringother)))letget_boolkwdargs=letk=match_kwdkwdargsinmatchget_param(Raw_option.Boolk)argswith|Processed_option.Bb->b|other->raise(Not_a_bool(k^" "^(Processed_option.to_stringother)))letget_set_boolkwdargs=trylet_=match_kwdkwdargsintruewithOption_is_mandatory_->falseletget_reset_boolkwdargs=not(get_set_boolkwdargs)(* optional options *)letget_int_optkwdargs=trySome(get_intkwdargs)withOption_is_mandatory_->Noneletget_string_optkwdargs=trySome(get_stringkwdargs)withOption_is_mandatory_->Noneletget_char_optkwdargs=trySome(get_charkwdargs)withOption_is_mandatory_->Noneletget_float_optkwdargs=trySome(get_floatkwdargs)withOption_is_mandatory_->Noneletget_bool_optkwdargs=trySome(get_boolkwdargs)withOption_is_mandatory_->None(* optional options with a default value *)letget_int_defkwdargsdef=matchget_int_optkwdargswith|None->def|Somei->iletget_string_defkwdargsdef=matchget_string_optkwdargswith|None->def|Somes->sletget_char_defkwdargsdef=matchget_char_optkwdargswith|None->def|Somec->cletget_float_defkwdargsdef=matchget_float_optkwdargswith|None->def|Somef->fletget_bool_defkwdargsdef=matchget_bool_optkwdargswith|None->def|Someb->b