123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544(**************************************************************************)(* *)(* This file is part of Frama-C. *)(* *)(* Copyright (C) 2007-2023 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* 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, version 2.1. *)(* *)(* It 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. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file licenses/LGPLv2.1). *)(* *)(**************************************************************************)(* -------------------------------------------------------------------------- *)moduleSenv=Server_parametersmoduleMd=Markdown(* -------------------------------------------------------------------------- *)typeplugin=Kernel|Pluginofstringtypeident={plugin:plugin;package:stringlist;name:string}letpp_stepfmta=(Format.pp_print_stringfmta;Format.pp_print_charfmt'.')letpp_pluginfmt=function|Kernel->pp_stepfmt"kernel"|Pluginp->pp_stepfmt"plugins";pp_stepfmtpletpp_identfmt{plugin;package;name}=(pp_pluginfmtplugin;List.iter(pp_stepfmt)package;Format.pp_print_stringfmtname)(* -------------------------------------------------------------------------- *)(* --- Name Resolution --- *)(* -------------------------------------------------------------------------- *)moduleStd=StdlibmoduleId=structtypet=identletcompare=Std.compareendmoduleIdMap=Map.Make(Id)moduleIdSet=Set.Make(Id)moduleNameSet=Set.Make(String)moduleScope=structletrecinpkgids=function|[]->ids|[p]->p::ids|_::ps->inpkgidspsletrelative~source~targetids=iftarget=sourcethenidselsematchtargetwith|Kernel->ids|Pluginp->p::idslettargetpids=matchpwith|Kernel->"kernel"::ids|Pluginp->"plugins"::p::ids(* propose various abbreviations ; finally render full qualified name *)letrankedsource{plugin;package;name}k=String.concat"_"@@letname=[name]inmatchkwith|0->name|1->relative~source~target:pluginname|2->relative~source~target:plugin(inpkgnamepackage)|3->relative~source~target:plugin(package@name)|_->targetplugin(package@name)typet={source:plugin;mutableclashes:bool;mutableindex:(string,intIdMap.t)Hashtbl.t;mutablenames:stringIdMap.t;mutablereserved:NameSet.t;}letcreatesource={source;index=Hashtbl.create0;clashes=false;names=IdMap.empty;reserved=NameSet.empty;}letrecnon_reservedscopeidrk=leta=rankedscope.sourceidrkinifNameSet.memascope.reservedthennon_reservedscopeid(succrk)elsea,rkletpushscopeidrk=beginletname,rk=non_reservedscopeidrkinscope.names<-IdMap.addidnamescope.names;letindex=scope.indexinmatchHashtbl.find_optindexnamewith|None->Hashtbl.addindexname(IdMap.addidrkIdMap.empty)|Someidks->ifIdMap.memididksthenscope.clashes<-trueelseHashtbl.replaceindexname(IdMap.addidrkidks)endletusescopeid=ifnot(IdMap.memidscope.names)thenpushscopeid0letreservescopename=assert(IdMap.is_emptyscope.names);scope.reserved<-NameSet.addnamescope.reservedletdeclarescopeid=beginlet{name}=idinifNameSet.memnamescope.reservedthenSenv.fatal"Reserved name for identifier '%a'"pp_identid;scope.names<-IdMap.addidnamescope.names;scope.reserved<-NameSet.addnamescope.reserved;endletrecresolvescope=ifnotscope.clashesthenscope.nameselsebeginletindex=scope.indexinscope.index<-Hashtbl.create0;scope.clashes<-false;Hashtbl.iter(fun_nameidks->matchIdMap.bindingsidkswith|[id,rk]->pushscopeidrk|idks->List.iter(fun(id,rk)->pushscopeid(succrk))idks)index;resolvescopeendend(* -------------------------------------------------------------------------- *)(* --- JSON Datatypes --- *)(* -------------------------------------------------------------------------- *)typejtype=|Jany|Jnull|Jboolean|Jnumber|Jstring|Jalpha(* string primarily compared without case *)|Jtagofstring(* single constant string *)|Jkeyofstring(* kind of a string used for indexing *)|Jindexofstring(* kind of an integer used for indexing *)|Joptionofjtype|Jdictofjtype(* dictionaries *)|Jarrayofjtype(* order matters *)|Jtupleofjtypelist|Junionofjtypelist|Jrecordof(string*jtype)list|Jenumofident*stringlist(* type and tags *)|Jdataofident*jtype(* type and definition *)|Jself(* for (simply) recursive types *)(* -------------------------------------------------------------------------- *)(* --- Declarations --- *)(* -------------------------------------------------------------------------- *)typefieldInfo={fd_name:string;fd_type:jtype;fd_descr:Markdown.text;}typetagInfo={tg_name:string;tg_label:Markdown.text;tg_descr:Markdown.text;}typeparamInfo=|P_valueofjtype|P_namedoffieldInfolisttyperequestInfo={rq_kind:[`GET|`SET|`EXEC];rq_input:paramInfo;rq_output:paramInfo;rq_signals:stringlist;}typearrayInfo={arr_key:string;arr_kind:jtype;arr_rows:jtype;}typedeclKindInfo=|D_signal|D_typeofjtype|D_enumoftagInfolist|D_recordoffieldInfolist|D_requestofrequestInfo|D_valueofjtype|D_stateofjtype|D_arrayofarrayInfo(* key kind *)|D_decoderofident*jtype|D_orderofident*jtype(* natural ordering *)|D_defaultofident*jtype(* default value *)typedeclInfo={d_ident:ident;d_descr:Markdown.text;d_kind:declKindInfo;}typepackageInfo={p_plugin:plugin;p_package:stringlist;p_title:string;p_descr:Markdown.text;p_readme:Filepath.Normalized.toption;p_content:declInfolist;}letname_of_ident?(sep=".")id=String.concatsep@@matchid.pluginwith|Kernel->"kernel"::id.package@[id.name]|Pluginp->"plugins"::p::(id.package@[id.name])letname_of_pkg?(sep=".")pluginpackage=String.concatsep@@matchpluginwith|Kernel->"kernel"::package|Pluginp->"plugins"::p::packageletname_of_pkginfo?sep{p_plugin;p_package}=name_of_pkg?sepp_pluginp_packageletpp_pkgnamefmt{p_plugin;p_package}=(pp_pluginfmtp_plugin;List.iter(pp_stepfmt)p_package)(* -------------------------------------------------------------------------- *)(* --- Derived Names --- *)(* -------------------------------------------------------------------------- *)letderived?prefix?suffixid=letcapitalize=String.capitalize_asciiinmatchprefix,suffixwith|None,None->id|Somep,None->{idwithname=p^capitalizeid.name}|None,Someq->{idwithname=id.name^q}|Somep,Someq->{idwithname=p^capitalizeid.name^q}moduleDerived=structletsignalid=derived~prefix:"signal"idletgetterid=derived~prefix:"get"idletsetterid=derived~prefix:"set"idletdataid=derived~suffix:"Data"idletdefaultid=derived~suffix:"Default"idletfetchid=derived~prefix:"fetch"idletreloadid=derived~prefix:"reload"idletorderid=derived~prefix:"by"idletdecodeid=derived~prefix:"j"idend(* -------------------------------------------------------------------------- *)(* --- Visitors --- *)(* -------------------------------------------------------------------------- *)letrecisRecursive=function|Jself->true|Jdata_|Jenum_|Jany|Jnull|Jboolean|Jnumber|Jstring|Jalpha|Jkey_|Jindex_|Jtag_->false|Joptionjs|Jdictjs|Jarrayjs->isRecursivejs|Jtuplejs|Junionjs->List.existsisRecursivejs|Jrecordfjs->List.exists(fun(_,js)->isRecursivejs)fjsletrecvisit_jtypefn=function|Jany|Jself|Jnull|Jboolean|Jnumber|Jstring|Jalpha|Jkey_|Jindex_|Jtag_->()|Joptionjs|Jdictjs|Jarrayjs->visit_jtypefnjs|Jtuplejs|Junionjs->List.iter(visit_jtypefn)js|Jrecordfjs->List.iter(fun(_,js)->visit_jtypefnjs)fjs|Jdata(id,_)|Jenum(id,_)->beginfnid;fn(Derived.defaultid);fn(Derived.decodeid);fn(Derived.orderid);endletvisit_fieldf{fd_type}=visit_jtypeffd_typeletvisit_paramf=function|P_valuejs->visit_jtypefjs|P_namedfds->List.iter(visit_fieldf)fdsletvisit_requestf{rq_input;rq_output}=(visit_paramfrq_input;visit_paramfrq_output)letvisit_dkindf=function|D_signal|D_enum_|D_array_->()|D_typejs|D_statejs|D_valuejs->visit_jtypefjs|D_decoder(id,js)|D_order(id,js)|D_default(id,js)->fid;visit_jtypefjs|D_recordfds->List.iter(visit_fieldf)fds|D_requestrq->visit_requestfrqletvisit_declf{d_kind}=visit_dkindfd_kindletvisit_package_declf{p_content}=List.iter(fun{d_ident}->fd_ident)p_contentletvisit_package_usedf{p_content}=List.iter(visit_declf)p_contentletresolve?(keywords=[])pkg=letscope=Scope.createpkg.p_plugininList.iter(Scope.reservescope)keywords;visit_package_decl(Scope.declarescope)pkg;visit_package_used(Scope.usescope)pkg;Scope.resolvescope(* -------------------------------------------------------------------------- *)(* --- Server API --- *)(* -------------------------------------------------------------------------- *)typepackage={pkgInfo:packageInfo;(* with empty decl *)mutablerevDecl:declInfolist;(* in reverse order *)}letfieldfd=fd.fd_name,fd.fd_typeletname_of_package?seppkg=name_of_pkginfo?seppkg.pkgInfoletregistry=refIdSet.empty(* including packages *)letpackages=ref[](* in reverse order *)letcollection=refNone(* computed *)letname_re=Str.regexp"^[a-zA-Z0-9]+$"letpackage_re=Str.regexp"^[a-z0-9]+\\(\\.[a-z0-9]+\\)*$"letcheck_packagepkg=ifnot(Str.string_matchpackage_repkg0)thenSenv.fatal"Invalid package identifier %S (use dot separated lowercase names)"pkgletcheck_namename=ifnot(Str.string_matchname_rename0)thenSenv.fatal"Invalid identifier %S (use « camlCased » names)"nameletregister_identid=ifIdSet.memid!registrythenSenv.fatal"Duplicate identifier '%a'"pp_identid;registry:=IdSet.addid!registryletresolve_readme~plugin=function|None->None|Somereadme->letfile=matchpluginwith|Kernel->Filepath.Normalized.concatsFc_config.datadir["server";"doc";readme]|Pluginname->Filepath.Normalized.concatsFc_config.datadir[name;"doc";readme]inSomefile(* -------------------------------------------------------------------------- *)(* --- Declarations --- *)(* -------------------------------------------------------------------------- *)letpackage?plugin?name~title?(descr=[])?readme()=letplugin=matchpluginwithNone->Kernel|Somep->Pluginpinletpkgname=matchnamewith|None->[]|Somepkg->check_packagepkg;String.split_on_char'.'pkginletpkgid={plugin;package=pkgname;name="*"}inletpkgInfo={p_plugin=plugin;p_package=pkgname;p_title=title;p_descr=descr;p_readme=resolve_readme~pluginreadme;p_content=[];}inletpackage={pkgInfo;revDecl=[]}inregister_identpkgid;collection:=None;packages:=package::!packages;packageletdeclare_id~package:pkg~name?(descr=[])decl=check_namename;let{p_plugin=plugin;p_package=package}=pkg.pkgInfoinletident={plugin;package;name}inletdecl={d_ident=ident;d_descr=descr;d_kind=decl}inregister_identident;pkg.revDecl<-decl::pkg.revDecl;identletdeclare~package~name?descrdecl=let_id=declare_id~package~name?descrdeclin()letupdate~package:pkg~namedecl=pkg.revDecl<-List.map(funcurr->ifcurr.d_ident.name=namethen{currwithd_kind=decl}elsecurr)pkg.revDeclletiterf=List.iterf@@match!collectionwith|Somepkgs->pkgs|None->letpkgs=List.sort(funab->Std.comparea.p_pluginb.p_plugin)@@List.rev_map(funpkg->{pkg.pkgInfowithp_content=List.revpkg.revDecl})!packagesincollection:=Somepkgs;pkgs(* -------------------------------------------------------------------------- *)(* --- JSON To MarkDown --- *)(* -------------------------------------------------------------------------- *)letkeykd=Md.plain(Printf.sprintf"`$%s`"kd)letindexkd=Md.plain(Printf.sprintf"`#%s`"kd)letlitteraltag=Md.plain(Printf.sprintf"`\"%s\"`"tag)typepp={self:Md.text;ident:ident->Md.text;}letrecmd_jtypepp=function|Jany->Md.emph"any"|Jself->pp.self|Jnull->Md.emph"null"|Jnumber->Md.emph"number"|Jboolean->Md.emph"boolean"|Jstring|Jalpha->Md.emph"string"|Jtaga->litterala|Jkeykd->keykd|Jindexkd->indexkd|Jdata(id,_)|Jenum(id,_)->pp.identid|Joptionjs->protectppjs@Md.code"?"|Jtuplejs->Md.code"["@md_jlistpp","js@Md.code"]"|Junionjs->md_jlistpp"|"js|Jarrayjs->protectppjs@Md.code"[]"|Jrecordfjs->Md.code"{"@fieldsppfjs@Md.code"}"|Jdictjs->Md.code"{[key]:"@md_jtypeppjs@Md.code"}"andmd_jlistppsepjs=Md.glue~sep:(Md.plainsep)(List.map(md_jtypepp)js)andfieldsppfjs=Md.glue~sep:(Md.plain",")@@List.map(fun(fd,js)->litteralfd@matchjswith|Joptionjs->Md.code":?"@md_jtypeppjs|_->Md.code":"@md_jtypeppjs)fjsandprotectnamesjs=matchjswith|Junion_->Md.code"("@md_jtypenamesjs@Md.code")"|_->md_jtypenamesjs(* -------------------------------------------------------------------------- *)(* --- Tags & Fields --- *)(* -------------------------------------------------------------------------- *)letmd_tags?(title="Tags")(tags:tagInfolist)=letheader=Md.[plaintitle,Left;plain"Value",Left;plain"Description",Left]inletrowtg=[tg.tg_label;litteraltg.tg_name;tg.tg_descr;]inMd.{caption=None;header;content=List.maprowtags}letmd_fields?(title="Field")pp(fields:fieldInfolist)=letheader=Md.[plaintitle,Left;plain"Format",Center;plain"Description",Left;]inletrowf=matchf.fd_typewith|Joptionjs->[litteralf.fd_name@Md.plain"(opt.)";md_jtypeppjs;f.fd_descr;]|_->[litteralf.fd_name;md_jtypeppf.fd_type;f.fd_descr;]inMd.{caption=None;header;content=List.maprowfields}(* -------------------------------------------------------------------------- *)(* --- Printer --- *)(* -------------------------------------------------------------------------- *)letpp_jtypefmtjs=letscope=Scope.createKernelinvisit_jtype(Scope.usescope)js;letns=Scope.resolvescopeinletself=Md.emph"self"inletidentid=Md.emph(IdMap.findidns)inMarkdown.pp_textfmt(md_jtype{self;ident}js)(* -------------------------------------------------------------------------- *)