123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382(* Header check
This module defines a some pipes to check the presence of headers in
an input file. Headerts are there to provide meta-data about problems,
such as version, source, classification, and even satisifability status
in some cases.
Note that this does *not* include the checking of constraints between
successivestatements such as what smtlib specifies, see flow.ml
The way this check is implemented/done:
- a number of meta-data fields are defines in module [Field],
together with ways to parse/print them.
- then each language defines its list of headers that are:
+ required (causing an error if they are absent)
+ wanted (causing a warning if they are absent)
- additionally, some checks are performed on the values of some
headers, particularly lang_version and license, which are
provided by the state (and usually controlled by the command
line, as there are not reasonable defaults for them).
- finally the pipe accumulates header (parsing them using the
functions from the [Field] module, and a checking function is
provided to check the presence of headers at the end of the
file processing.
Note that the current way means that headers can theoretically be
present anywhere in the file, rather than at the beginning.
*)(* Header fields *)(* ************************************************************************ *)moduleField=structtypet=|Lang_version|Problem_version|Problem_source|Problem_license|Problem_category|Problem_statusletequal=(=)letcompare=comparelethash=Hashtbl.hash(* Correspondance between fields and their names in languages *)letnamelangfield=matchlang,fieldwith|SomeLogic.Smtlib2_,Lang_version->":smt-lib-version"|SomeLogic.Smtlib2_,Problem_source->":source"|SomeLogic.Smtlib2_,Problem_license->":license"|SomeLogic.Smtlib2_,Problem_category->":category"|SomeLogic.Smtlib2_,Problem_status->":status"|_,Lang_version->"Language version"|_,Problem_version->"Problem version"|_,Problem_source->"Problem source"|_,Problem_license->"Problem license"|_,Problem_category->"Problem_category"|_,Problem_status->"Problem status"letprint?langfmtfield=Format.fprintffmt"%s"(namelangfield)(* Parse an attribute into an (optional) field and value. *)moduleId=Dolmen.Std.IdmoduleLoc=Dolmen.Std.LocmoduleAst=Dolmen.Std.Termtyperes=|Okoft*string|ErrorofLoc.t*string|Not_a_headermoduleSmtlib2=structletcheck_version_numberversion=ifString.lengthversion>=3&&String.subversion02="2."thenbegintrylets=String.subversion2(String.lengthversion-2)inlet_=int_of_stringsintruewithFailure_->falseendelsefalseletrecparse=function|{Ast.term=Ast.App({Ast.term=Ast.Symbols;_},args);loc;_}->parse_auxlocsargs|_->Not_a_headerandparse_auxlocsargs=matchswith(* Language version *)|{Id.ns=Attr;Id.name=Simple":smt-lib-version";}->beginmatchargswith|[{Ast.term=Ast.Symbol{Id.ns=ValueReal;Id.name=Simpleversion;};_}]->ifcheck_version_numberversionthenOk(Lang_version,version)elseError(loc,":smt-lib-version number must be in the form 2.X")|[]->Error(loc,"empty value for :smt-lib-version")|{Ast.loc;_}::_->Error(loc,"Expected a version number")end(* Problem source *)|{Id.ns=Attr;Id.name=Simple":source";}->beginmatchargswith|[{Ast.term=Ast.Symbol{Id.ns=Attr;Id.name=Simpledescr};_}]->Ok(Problem_source,descr)|[]->Error(loc,"empty value for :source")|{Ast.loc;_}::_->Error(loc,"Expected a single symbol as description")end(* Problem license *)|{Id.ns=Attr;Id.name=Simple":license";}->beginmatchargswith|[{Ast.term=Ast.Symbol{Id.ns=ValueString;Id.name=Simplelicense};_}]->Ok(Problem_license,license)|[]->Error(loc,"empty value for :license")|{Ast.loc;_}::_->Error(loc,"Expected a single string in quotes")end(* Problem category *)|{Id.ns=Attr;Id.name=Simple":category";}->beginmatchargswith|[{Ast.term=Ast.Symbol{Id.ns=ValueString;Id.name=Simple(("crafted"|"random"|"industrial")ascategory)};_};]->Ok(Problem_category,category)|[]->Error(loc,"empty value for :category")|{Ast.loc;_}::_->Error(loc,{|Expected "crafted", "random", or "industrial" (in quotes)|})end(* Problem status *)|{Id.ns=Attr;Id.name=Simple":status";}->beginmatchargswith|[{Ast.term=Ast.Symbol{Id.name=Simple(("sat"|"unsat"|"unknown")asstatus);_};_};]->Ok(Problem_status,status)|_->Error(loc,"Expected sat|unsat|unknown")end(* catch-all *)|_->Not_a_headerendletparse?langt=matchlangwith|SomeLogic.Smtlib2_->Smtlib2.parset|_->Not_a_headerend(* Header errors & warnings *)(* ************************************************************************ *)letcode=Code.create~category:"Header"~descr:"on header errors"letmissing_header_error=Report.Error.mk~code~mnemonic:"header-missing"~message:(funfmt(lang,missing)->letpp_sepfmt()=Format.fprintffmt",@ "inFormat.fprintffmt"The following header fields are missing: %a"(Format.pp_print_list~pp_sep(Field.print?lang))missing)~name:"Missing header statement"()letinvalid_header_value_error=Report.Error.mk~code~mnemonic:"header-invalid-value"~message:(funfmt(field,lang,msg)->Format.fprintffmt"Invalid value for header %a: %s"(Field.print?lang)fieldmsg)~name:"Invalid header value"()letbad_header_payload=Report.Error.mk~code~mnemonic:"header-bad-payload"~message:(funfmtmsg->Format.fprintffmt"Could not parse the header: %s"msg)~name:"Incorrect header payload"()letempty_header_field=Report.Warning.mk~code~mnemonic:"empty-header-field"~message:(funfmt(lang,l)->letpp_sepfmt()=Format.fprintffmt",@ "inFormat.fprintffmt"The following header fields are missing and thus \
default values will be assumed: %a"(Format.pp_print_list~pp_sep(Field.print?lang))l)~name:"Header field with a missing value"()(* Headers *)(* ************************************************************************ *)moduleM=Map.Make(Field)typet={fields:stringM.t;}letempty={fields=M.empty;}letsethfv={fields=M.addfvh.fields;}letremovehf={fields=M.removefh.fields;}letgethf=trySome(M.findfh.fields)withNot_found->Noneletmemhf=M.memfh.fields(* Required headers for languages *)(* ************************************************************************ *)letsmtlib2_required:Field.tlist=[Lang_version;Problem_source;Problem_category;(* Problem_status is checked for every check-sat *)]letsmtlib2_wanted:Field.tlist=[Problem_license;](* Required headers for languages *)(* ************************************************************************ *)moduletypeS=Headers_intf.SmoduleMake(State:State.S)=structletpipe="Headers"letheader_check:boolState.key=State.create_key~pipe"header_check"letheader_state:tState.key=State.create_key~pipe"header_state"letheader_licenses:stringlistState.key=State.create_key~pipe"header_licenses"letheader_lang_version:stringoptionState.key=State.create_key~pipe"header_lang_version"letinit~header_check:header_check_value?header_state:(header_state_value=empty)~header_licenses:header_licenses_value~header_lang_version:header_lang_version_valuest=st|>State.setheader_checkheader_check_value|>State.setheader_stateheader_state_value|>State.setheader_licensesheader_licenses_value|>State.setheader_lang_versionheader_lang_version_value(* Final check for headers *)letcheck_wantedsth=letlang=(State.getState.logic_filest).langinletwanted=matchlangwith|SomeLogic.Smtlib2_->smtlib2_wanted|_->[]inmatchList.filter(funf->not(memhf))wantedwith|[]->st|missing->State.warnstempty_header_field(lang,missing)letcheck_requiredsth=letlang=(State.getState.logic_filest).langinletrequired=matchlangwith|SomeLogic.Smtlib2_->smtlib2_required|_->[]inmatchList.filter(funf->not(memhf))requiredwith|[]->st|missing->State.errorstmissing_header_error(lang,missing)letcheckst=ifnot(State.getheader_checkst)thenstelsebeginleth=State.getheader_statestinletst=check_wantedsthinletst=check_requiredsthinstend(* Incremental checks and construction of the header set *)leterrorstlocerrparam=letfile=(State.getState.logic_filest).locinletloc:Dolmen.Std.Loc.full={file;loc;}inState.error~locsterrparamletinvalid_header_valuestlocfieldmsg=letlang=(State.getState.logic_filest).langinerrorstlocinvalid_header_value_error(field,lang,msg)letcheck_headerstlocfieldvalue=match(field:Field.t)with|Lang_version->beginmatchState.getheader_lang_versionstwith|None->st|Somev->ifv=valuethenstelseinvalid_header_valuestlocLang_version(Format.sprintf"language version must be: %s"v)end|Problem_license->beginmatchState.getheader_licensesstwith|[]->st|allowed->ifList.memvalueallowedthenstelseinvalid_header_valuestlocProblem_license"this license is not in the list of allowed licenses"end|_->stletinspectstc=ifnot(State.getheader_checkst)then(st,c)elsebeginletlang=(State.getState.logic_filest).langinleth=State.getheader_statestinletst=match(c:Dolmen.Std.Statement.t)with|{descr=Set_infot;loc;_}->beginmatchField.parse?langtwith|Not_a_header->st|Error(loc,msg)->errorstlocbad_header_payloadmsg|Ok(field,value)->letst=check_headerstlocfieldvalueinletst=State.setheader_state(sethfieldvalue)stinstend|{descr=Prove_;loc;_}->ifmemhProblem_statusthenState.setheader_state(removehProblem_status)stelseerrorstlocmissing_header_error(lang,[Problem_status])|_->stinst,cendend