123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155moduleBcfg_query=Bcfg_queryleterror_msgffmt=Format.kasprintf(funmsg->Error(`Msgmsg))fmt(* An index ([foo[0]]) is lexed as a plain word: it is up to us to check that
it is a valid number. The walk rejects the query upfront so that {!eval}
never has to deal with an invalid (or overflowing) index. *)letrecvalidate_pattern=letopenBcfg_queryinfunction|PWord_|PAny->Ok()|PEvale->validate_expre|PNotp->validate_patternp|PAnd(a,b)|POr(a,b)->Result.bind(validate_patterna)(fun()->validate_patternb)andvalidate_expr=letopenBcfg_queryinfunction|EWord_->Ok()|EPatternp->validate_patternp|EGet_parameter(e,idx)->beginmatchint_of_string_optidxwith|Somenwhenn>=0->validate_expre|_->Error(`Msg(Printf.sprintf"Invalid index %S in the query"idx))end|EGet_subdirective(a,b)->Result.bind(validate_expra)(fun()->validate_exprb)|EDirective(e,p)->Result.bind(validate_expre)(fun()->validate_patternp)|EParameter(p,e)|EChild(p,e)|ENot_parameter(p,e)|ENot_child(p,e)->Result.bind(validate_patternp)(fun()->validate_expre)letof_stringstr=letlexbuf=Lexing.from_stringstrinmatchBcfg_query_parser.queryBcfg_query_lexer.tokenlexbufwith|query->Result.bind(validate_exprquery)(fun()->Okquery)|exceptionBcfg_query_parser.Error->error_msgf"Invalid query: %S"str|exceptionBcfg_query_lexer.Unexpected_characterchr->error_msgf"Invalid character %C in the query"chr|exceptionBcfg_query_lexer.Unterminated_quote->error_msgf"Unterminated quote in the query: %S"strletpp=Bcfg_query.pp_expr(* A query is [is_streamable] when it contains no [@(...)] substitution: such a
query never looks outside the current directive's subtree, so it can be
evaluated one top-level directive at a time (see {!Bcfg.Stream.to_directives})
without materialising the whole document. *)letreceval_in_pattern=letopenBcfg_queryinfunction|PWord_|PAny->false|PEval_->true|PNotp->eval_in_patternp|PAnd(a,b)|POr(a,b)->eval_in_patterna||eval_in_patternbandeval_in_expr=letopenBcfg_queryinfunction|EWord_->false|EPatternp->eval_in_patternp|EGet_parameter(e,_)->eval_in_expre|EGet_subdirective(a,b)->eval_in_expra||eval_in_exprb|EDirective(e,p)->eval_in_expre||eval_in_patternp|EParameter(p,e)|EChild(p,e)|ENot_parameter(p,e)|ENot_child(p,e)->eval_in_patternp||eval_in_expreletis_streamablequery=not(eval_in_exprquery)(* The string value of a directive, as used inside a [$(...)] substitution. As
noted in the design, [$(foo.bar)] behaves like [foo.bar[0]]: the value is the
first parameter, and falls back to the directive name when there is none
(which is precisely what [\[0\]] produces). *)letvalue_of_directive{Bcfg.name;parameters;_}=matchparameterswithp::_->p|[]->name(* [predicate] and [eval] are mutually recursive: a [$(...)] pattern evaluates an
expression against [root] (the whole document, so it can reference any part of
it) and matches a string against the resulting values. *)letrecpredicate~rootpatternstr=letopenBcfg_queryinmatchpatternwith|PWordword->String.equalwordstr|PAny->true|PEvalexpr->letds=eval~rootexprrootinList.exists(fund->String.equal(value_of_directived)str)ds|PNotp->not(predicate~rootpstr)|PAnd(a,b)->predicate~rootastr&&predicate~rootbstr|POr(a,b)->predicate~rootastr||predicate~rootbstrandeval~rootquerybcfg=letopenBcfg_queryinmatchquerywith|EWordword->letfn{Bcfg.name;_}=name=wordinList.filterfnbcfg|EGet_subdirective(a,b)->letbcfg=eval~rootabcfginletfn{Bcfg.children;_}=eval~rootbchildreninList.concat_mapfnbcfg|EGet_parameter(a,idx)->beginletbcfg=eval~rootabcfgin(* [idx] was checked by [validate_expr]: it is a valid number. *)matchint_of_string_optidxwith|None->[]|Someidx->letfn{Bcfg.parameters;children;_}=matchList.nth_optparametersidxwith|Somename->Some{Bcfg.name;parameters=[];children}|None->NoneinList.filter_mapfnbcfgend|EDirective(a,p)->letpred=predicate~rootpinletfn{Bcfg.name;_}=prednameinletbcfg=eval~rootabcfginList.filterfnbcfg|EParameter(p,a)->letpred=predicate~rootpinletfn{Bcfg.parameters;_}=List.existspredparametersinletbcfg=List.filterfnbcfgineval~rootabcfg|EChild(p,a)->(* keep directives that contain a child whose name matches [p] *)letpred=predicate~rootpinletfn{Bcfg.children;_}=List.exists(func->predc.Bcfg.name)childreninletbcfg=List.filterfnbcfgineval~rootabcfg|ENot_parameter(p,a)->(* anti-join: keep directives with NO parameter matching [p] *)letpred=predicate~rootpinletfn{Bcfg.parameters;_}=not(List.existspredparameters)inletbcfg=List.filterfnbcfgineval~rootabcfg|ENot_child(p,a)->(* anti-join: keep directives with NO child matching [p] *)letpred=predicate~rootpinletfn{Bcfg.children;_}=not(List.exists(func->predc.Bcfg.name)children)inletbcfg=List.filterfnbcfgineval~rootabcfg|EPatternp->letpred=predicate~rootpinletfn{Bcfg.name;_}=prednameinList.filterfnbcfgletevalquerybcfg=eval~root:bcfgquerybcfg