123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614(*****************************************************************************
Liquidsoap, a programmable audio stream generator.
Copyright 2003-2024 Savonet team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details, fully stated in the COPYING
file at the root of the liquidsoap distribution.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************)(** Terms and values in the Liquidsoap language. *)(** An internal error. Those should not happen in theory... *)exceptionInternal_errorof(Pos.tlist*string)(** A parsing error. *)exceptionParse_errorof(Pos.t*string)(** Unsupported encoder *)exceptionUnsupported_encoderof(Pos.toption*string)let()=Printexc.register_printer(function|Internal_error(pos,e)->Some(Printf.sprintf"Lang_values.Internal_error at %s: %s"(Pos.List.to_stringpos)e)|Parse_error(pos,e)->Some(Printf.sprintf"Lang_values.Parse_error at %s: %s"(Pos.to_stringpos)e)|Unsupported_encoder(pos,e)->Some(Printf.sprintf"Lang_values.Unsupported_encoder at %s: %s"(Pos.Option.to_stringpos)e)|_->None)letconf_debug=reffalseletconf_debug_errors=reffalse(** Are we in debugging mode? *)letdebug=Lazy.from_fun(fun()->tryignore(Sys.getenv"LIQUIDSOAP_DEBUG_LANG");truewithNot_found->!conf_debug)(* We want to keep this a reference and not a dtools and not something more
complicated (e.g. dtools) in order not to impact performances. *)letprofile=reffalseletref_t?post=Type.make?pos(* The type has to be invariant because we don't want the sup mechanism to be used here, see #2806. *)(Type.Constr{Type.constructor="ref";params=[(`Invariant,t)]})(** {2 Terms} *)(** Sets of variables. *)moduleVars=Set.Make(String)moduleGround=structtypet=..typecontent={descr:t->string;to_json:pos:Pos.tlist->t->Json.t;compare:t->t->int;typ:(moduleType.Ground.Custom);}lethandlers=Hashtbl.create10letregistermatcherc=letmoduleC=(valc.typ:Type.Ground.Custom)inHashtbl.replacehandlersC.Type(c,matcher)exceptionFoundofcontentletfindv=tryHashtbl.iter(fun_(c,matcher)->ifmatchervthenraise(Foundc))handlers;raiseNot_foundwithFoundc->cletto_string(v:t)=(findv).descrvletto_json(v:t)=(findv).to_jsonvletto_descr(v:t)=letmoduleC=(val(findv).typ:Type.Ground.Custom)inC.descrletto_type(v:t)=letmoduleC=(val(findv).typ:Type.Ground.Custom)inC.Typeletcompare(v:t)=(findv).comparevtypet+=Boolofbool|Intofint|Stringofstring|Floatoffloatlet()=letcompareconvvv'=Stdlib.compare(convv)(convv')inletto_bool=functionBoolb->b|_->assertfalseinletto_stringb=string_of_bool(to_boolb)inletto_json~pos:_b=`Bool(to_boolb)inregister(functionBool_->true|_->false){descr=to_string;to_json;compare=compareto_bool;typ=(moduleType.Ground.Bool:Type.Ground.Custom);};letto_int=functionInti->i|_->assertfalseinletto_stringi=string_of_int(to_inti)inletto_json~pos:_i=`Int(to_inti)inregister(functionInt_->true|_->false){descr=to_string;to_json;compare=compareto_int;typ=(moduleType.Ground.Int:Type.Ground.Custom);};letto_string=function|Strings->Lang_string.quote_strings|_->assertfalseinletto_json~pos:_=functionStrings->`Strings|_->assertfalseinregister(functionString_->true|_->false){descr=to_string;to_json;compare=compare(functionStrings->s|_->assertfalse);typ=(moduleType.Ground.String:Type.Ground.Custom);};letto_float=functionFloatf->f|_->assertfalseinletto_json~pos:_f=`Float(to_floatf)inregister(functionFloat_->true|_->false){descr=(funf->string_of_float(to_floatf));to_json;compare=compareto_float;typ=(moduleType.Ground.Float:Type.Ground.Custom);}endmoduletypeGroundDef=sigtypecontentvaldescr:content->stringvalto_json:pos:Pos.tlist->content->Json.tvalcompare:content->content->intvaltyp:(moduleType.Ground.Custom)endmoduleMkGround(D:GroundDef)=structtypeGround.t+=GroundofD.contentlet()=letto_ground=functionGroundg->g|_->assertfalseinletto_json~posv=D.to_json~pos(to_groundv)inletcomparevv'=D.compare(to_groundv)(to_groundv')inletdescrv=D.descr(to_groundv)inGround.register(functionGround_->true|_->false){Ground.typ=D.typ;to_json;compare;descr}endmoduleMethods=structincludeMethodstype'atyp=(string,'a)ttype'at='atypendtypet={mutablet:Type.t;term:in_term;methods:tMethods.t;id:int}(** Documentation for declarations: general documentation, parameters, methods. *)anddoc=Doc.Value.tandlet_t={doc:docoption;(* name, arguments, methods *)replace:bool;(* whether the definition replaces a previously existing one (keeping methods) *)pat:pattern;mutablegen:Type.varlist;def:t;body:t;}andencoder_params=(string*[`Termoft|`Encoderofencoder])list(** A formal encoder. *)andencoder=string*encoder_paramsandinvoke={invoked:t;default:toption;meth:string}andin_term=|GroundofGround.t|Encoderofencoder|Listoftlist|Tupleoftlist|Null|Castoft*Type.t|Invokeofinvoke|Openoft*t|Letoflet_t|Varofstring|Seqoft*t|Appoft*(string*t)list(* [fun ~l1:x1 .. ?li:(xi=defi) .. -> body] =
* [Fun (V, [(l1,x1,None)..(li,xi,Some defi)..], body)]
* The first component [V] is the list containing all
* variables occurring in the function. It is used to
* restrict the environment captured when a closure is
* formed. *)|FunofVars.t*(string*string*Type.t*toption)list*t(* A recursive function, the first string is the name of the recursive
variable. *)|RFunofstring*Vars.t*(string*string*Type.t*toption)list*tandpattern=|PVarofstringlist(** a field *)|PTupleofpatternlist(** a tuple *)|PListof(patternlist*stringoption*patternlist)(** a list *)|PMethof(patternoption*(string*patternoption)list)(** a value with methods *)typeterm=tletunit=Tuple[](* Only used for printing very simple functions. *)letrecis_groundx=matchx.termwith|Listl|Tuplel->List.for_allis_groundl|Null|Ground_->true|_->falseletrecstring_of_pat=function|PVarl->String.concat"."l|PTuplel->"("^String.concat", "(List.mapstring_of_patl)^")"|PList(l,spread,l')->"["^String.concat", "(List.mapstring_of_patl@(matchspreadwithNone->[]|Somev->["..."^v])@List.mapstring_of_patl')^"]"|PMeth(pat,l)->(matchpatwithNone->""|Somepat->string_of_patpat^".")^"{"^String.concat", "(List.map(fun(lbl,pat)->matchpatwith|None->lbl|Somepat->lbl^": "^string_of_patpat)l)^"}"(** String representation of terms, (almost) assuming they are in normal
form. *)letrecto_stringv=letto_base_stringv=matchv.termwith|Groundg->Ground.to_stringg|Encodere->letrecaux(e,p)=letp=p|>List.map(function|"",`Termv->to_stringv|l,`Termv->l^"="^to_stringv|_,`Encodere->auxe)|>String.concat", "in"%"^e^"("^p^")"inauxe|Listl->"["^String.concat", "(List.mapto_stringl)^"]"|Tuplel->"("^String.concat", "(List.mapto_stringl)^")"|Null->"null"|Cast(e,t)->"("^to_stringe^" : "^Repr.string_of_typet^")"|Invoke{invoked=e;meth=l;default}->(matchdefaultwith|None->to_stringe^"."^l|Somev->"("^to_stringe^"."^l^" ?? "^to_stringv^")")|Open(m,e)->"open "^to_stringm^" "^to_stringe|Fun(_,[],v)whenis_groundv->"{"^to_stringv^"}"|Fun_|RFun_->"<fun>"|Vars->s|App(hd,tl)->lettl=List.map(fun(lbl,v)->(iflbl=""then""elselbl^" = ")^to_stringv)tlinto_stringhd^"("^String.concat","tl^")"(* | Let _ | Seq _ -> assert false *)|Letl->Printf.sprintf"let %s = %s in %s"(string_of_patl.pat)(to_stringl.def)(to_stringl.body)|Seq(e,e')->to_stringe^"; "^to_stringe'inletterm=to_base_stringvinifMethods.is_emptyv.methodsthentermelse(letmethods=Methods.bindingsv.methodsin(ifv.term=Tuple[]then""elseterm^".")^"{"^String.concat", "(List.map(fun(l,meth_term)->l^"="^to_stringmeth_term)methods)^"}")moduleActiveTerm=Active_value.Make(structtypetyp=ttypet=typletid{id}=idend)letactive_terms=ActiveTerm.create1024lettrim_runtime_types()=ActiveTerm.iter(funterm->term.t<-Type.deep_demethterm.t)active_termsletid=letcounter=Atomic.make0infun()->Atomic.fetch_and_addcounter1(** Create a new value. *)letmake?pos?t?(methods=Methods.empty)e=letid=id()inlett=matchtwithSomet->t|None->Type.var?pos()inifLazy.forcedebugthenPrintf.eprintf"%s (%s): assigned type var %s\n"(Pos.Option.to_stringt.Type.pos)(tryto_string{t;term=e;methods;id}with_->"<?>")(Repr.string_of_typet);letterm={t;term=e;methods;id}inActiveTerm.addactive_termsterm;termletrecfree_vars_pat=function|PVar[]->assertfalse|PVar[_]->Vars.empty|PVar(x::_)->Vars.singletonx|PTuplel->List.fold_leftVars.unionVars.empty(List.mapfree_vars_patl)|PList(l,spread,l')->List.fold_leftVars.unionVars.empty(List.mapfree_vars_pat(l@(matchspreadwithNone->[]|Somev->[PVar[v]])@l'))|PMeth(pat,l)->List.fold_leftVars.union(matchpatwithNone->Vars.empty|Somepat->free_vars_patpat)(List.mapfree_vars_pat(List.fold_left(funcur(lbl,pat)->[PVar[lbl]]@(matchpatwithNone->[]|Somepat->[pat])@cur)[]l))letrecbound_vars_pat=function|PVar[]->assertfalse|PVar[x]->Vars.singletonx|PVar_->Vars.empty|PTuplel->List.fold_leftVars.unionVars.empty(List.mapbound_vars_patl)|PList(l,spread,l')->List.fold_leftVars.unionVars.empty(List.mapbound_vars_pat(l@(matchspreadwithNone->[]|Somev->[PVar[v]])@l'))|PMeth(pat,l)->List.fold_leftVars.union(matchpatwithNone->Vars.empty|Somepat->bound_vars_patpat)(List.mapbound_vars_pat(List.fold_left(funcur(lbl,pat)->[PVar[lbl]]@(matchpatwithNone->[]|Somepat->[pat])@cur)[]l))letrecfree_varstm=letroot_free_vars=function|Ground_->Vars.empty|Varx->Vars.singletonx|Tuplel->List.fold_left(funva->Vars.unionv(free_varsa))Vars.emptyl|Null->Vars.empty|Encodere->letrecenc(_,p)=List.fold_left(funv(_,t)->matchtwith|`Termt->Vars.unionv(free_varst)|`Encodere->Vars.unionv(ence))Vars.emptypinence|Cast(e,_)->free_varse|Seq(a,b)->Vars.union(free_varsa)(free_varsb)|Invoke{invoked=e;default}->Vars.union(free_varse)(matchdefaultwithNone->Vars.empty|Somed->free_varsd)|Open(a,b)->Vars.union(free_varsa)(free_varsb)|Listl->List.fold_left(funvt->Vars.unionv(free_varst))Vars.emptyl|App(hd,l)->List.fold_left(funv(_,t)->Vars.unionv(free_varst))(free_varshd)l|RFun(_,fv,_,_)|Fun(fv,_,_)->fv|Letl->Vars.union(free_varsl.def)(Vars.diff(free_varsl.body)(bound_vars_patl.pat))inMethods.fold(fun_meth_termfv->Vars.unionfv(free_varsmeth_term))tm.methods(root_free_varstm.term)letfree_vars?(bound=[])body=Vars.diff(free_varsbody)(Vars.of_listbound)(** Values which can be ignored (and will thus not raise a warning if
ignored). *)letcan_ignoret=match(Type.demetht).Type.descrwith|Type.Tuple[]|Type.Var_->true|_->false(** {1 Basic checks and errors} *)(** Trying to use an unbound variable. *)exceptionUnboundofPos.Option.t*string(** Silently discarding a meaningful value. *)exceptionIgnoredoft(** [No_label (f,lbl,first,x)] indicates that the parameter [x] could not be
passed to the function [f] because the latter has no label [lbl]. The
[first] information tells whether [lbl=x] is the first parameter with label
[lbl] in the considered application, which makes the message a bit more
helpful. *)exceptionNo_labeloft*string*bool*t(** A function defines multiple arguments with the same label. *)exceptionDuplicate_labelofPos.Option.t*string(** Some mandatory arguments with given label and typed were not passed to the
function during an application. *)exceptionMissing_argumentsofPos.Option.t*(string*Type.t)list(** Check that all let-bound variables are used. No check is performed for
variable arguments. This cannot be done at parse-time (as for the
computation of the free variables of functions) because we need types, as
well as the ability to distinguish toplevel and inner let-in terms. *)exceptionUnused_variableof(string*Pos.t)letcheck_unused~throw~libtm=letreccheck?(toplevel=false)vtm=letv=Methods.fold(fun_meth_terme->checkemeth_term)tm.methodsvinmatchtm.termwith|Vars->Vars.removesv|Ground_->v|Tuplel->List.fold_left(funa->checka)vl|Null->v|Cast(e,_)->checkve|Invoke{invoked=e}->checkve|Open(a,b)->check(checkva)b|Seq(a,b)->check~toplevel(checkva)b|Listl->List.fold_left(funxy->checkxy)vl|Encodere->letrecencv(_,p)=List.fold_left(funv(_,t)->matchtwith`Termt->checkvt|`Encodere->encve)vpinencve|App(hd,l)->letv=checkvhdinList.fold_left(funv(_,t)->checkvt)vl|RFun(_,arg,p,body)->checkv{tmwithterm=Fun(arg,p,body)}|Fun(_,p,body)->letv=List.fold_left(funv->function|_,_,_,Somedefault->checkvdefault|_->v)vpinletbound=List.fold_left(funv(_,var,_,_)->Vars.addvarv)Vars.emptypinletmasked=Vars.intervboundinletv=Vars.unionvboundinletv=checkvbodyinVars.iter(funx->ifVars.memxv&&x<>"_"thenthrow(Unused_variable(x,Option.gettm.t.Type.pos)))bound;(* Restore masked variables. The masking variables have been used but
it does not count for the ones they masked. Bound variables have
been handled above. *)Vars.unionmasked(Vars.diffvbound)|Let{pat;def;body;_}->letv=checkvdefinletbvpat=bound_vars_patpatinletmask=Vars.intervbvpatinletv=Vars.unionvbvpatinletv=check~toplevelvbodyinif(* Do not check for anything at toplevel in libraries *)not(toplevel&&lib)thenVars.iter(funs->(* Do we have an unused definition? *)ifVars.memsvthen(* There are exceptions: unit and functions when
at toplevel (sort of a lib situation...) *)ifs<>"_"&¬(can_ignoredef.t||(toplevel&&Type.is_fundef.t))thenthrow(Unused_variable(s,Option.gettm.t.Type.pos)))bvpat;Vars.unionvmaskin(* Unused free variables may remain *)ignore(check~toplevel:trueVars.emptytm)(* Abstract types. *)moduletypeAbstract=sigtypecontentvalt:Type.tvalto_ground:content->Ground.tvalof_ground:Ground.t->contentvalis_ground:Ground.t->boolvalto_term:content->tvalof_term:t->contentvalis_term:t->boolendmoduletypeAbstractDef=sigtypecontentvalname:stringvalto_json:pos:Pos.tlist->content->Json.tvaldescr:content->stringvalcompare:content->content->intendmoduleMkAbstract(Def:AbstractDef)=structmoduleT=Type.Ground.Make(structletname=Def.nameend)typeGround.t+=ValueofDef.contentlet()=letto_value=functionValuev->v|_->assertfalseinletcomparevv'=Def.compare(to_valuev)(to_valuev')inletdescrv=Def.descr(to_valuev)inletto_json~posv=Def.to_json~pos(to_valuev)inGround.register(functionValue_->true|_->false){Ground.descr;to_json;compare;typ=(moduleT:Type.Ground.Custom)}typecontent=Def.contentlett=Type.makeT.descrletof_ground=functionValuec->c|_->assertfalseletto_groundc=Valuecletis_ground=functionValue_->true|_->falseletof_termt=matcht.termwithGround(Valuec)->c|_->assertfalseletto_termc={t=Type.makeT.descr;term=Ground(Valuec);methods=Methods.empty;id=id();}letis_termt=matcht.termwithGround(Value_)->true|_->falseend