123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188(* Yoann Padioleau
*
* Copyright (C) 2013 Facebook
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library 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 file
* license.txt for more details.
*)openCommonmoduleMV=Metavars_fuzzy(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*
* This module makes it possible to match one Tree against another Tree
* providing a kind of grep but at a syntactical level.
*)(*****************************************************************************)(* Wrappers *)(*****************************************************************************)letpr2,_pr2_once=Common2.mk_pr2_wrappersFlag_matcher.verbose(*****************************************************************************)(* The functor argument *)(*****************************************************************************)moduleXMATCH=struct(* ------------------------------------------------------------------------*)(* Combinators history *)(* ------------------------------------------------------------------------*)(*
* version0:
* type ('a, 'b) matcher = 'a -> 'b -> bool
*
* This just lets you know if you matched something.
*
* version1:
* type ('a, 'b) matcher = 'a -> 'b -> unit -> ('a, 'b) option
*
* The Maybe monad.
*
* version2:
* type ('a, 'b) matcher = 'a -> 'b -> binding -> binding list
*
* Why not returning a binding option ? because I may need at some
* point to return multiple possible bindings for one matching code.
* For instance with the pattern do 'f(..., X, ...)', X could be binded
* to different parts of the code.
*
* Note that the empty list means a match failure.
*)typetin=MV.fuzzy_bindingtype'xtout=('x*MV.fuzzy_binding)listtype('a,'b)matcher='a->'b->tin->('a*'b)toutlet((>>=):(tin->('a*'b)tout)->(('a*'b)->(tin->('c*'d)tout))->(tin->('c*'d)tout))=funm1m2->funtin->(* old:
match m1 tin with
| None -> None
| Some (a,b) ->
m2 (a, b) tin
*)(* let's get a list of possible environment match (could be
* the empty list when it didn't match, playing the role None
* had before)
*)letxs=m1tinin(* try m2 on each possible returned bindings *)letxxs=xs|>List.map(fun((a,b),binding)->m2(a,b)binding)inList.flattenxxslet(>||>)m1m2=funtin->(* CHOICE
let xs = m1 tin in
if null xs
then m2 tin
else xs
*)(* opti? use set instead of list *)m1tin@m2tinletreturn(a,b)=funtin->(* old: Some (a,b) *)[(a,b),tin]letfail=fun_tin->(* old: None *)[](* ------------------------------------------------------------------------*)(* Environment *)(* ------------------------------------------------------------------------*)lettokenfab=leta1=Parse_info.str_of_infoainletb1=Parse_info.str_of_infobinifa1=$=b1thenreturn(a,b)elsefail(* pre: both 'a' and 'b' contains only regular PHP code. There is no
* metavariables in them.
* coupling: don't forget to also modify the one in transforming_fuzzy.ml
* todo: factorize code
*)letequal_ast_binded_codeab=(* Note that because we want to retain the position information
* of the matched code in the environment (e.g. for the -pvar
* sgrep command line argument), we can not just use the
* generic '=' OCaml operator as 'a' and 'b' may represent
* the same code but they will contain leaves in their AST
* with different position information. So before doing
* the comparison we just need to remove/abstract-away
* the line number information in each ASTs.
*
* less: optimize by caching the abstract_lined ?
*)leta=Lib_ast_fuzzy.abstract_position_treesainletb=Lib_ast_fuzzy.abstract_position_treesbina=*=bletcheck_and_add_metavar_binding((mvar:string),valu)=funtin->matchCommon2.assoc_optmvartinwith|Somevalu'->(* Should we use fuzzy_vs_fuzzy itself for comparing the binded code ?
* Hmmm, we can't because it leads to a circular dependencies.
* Moreover here we know both valu and valu' are regular code,
* not patterns, so we can just use the generic '=' of OCaml.
*)ifequal_ast_binded_codevaluvalu'thenSometinelseNone|None->(* first time the metavar is binded, just add it to the environment *)Some(Common2.insert_assoc(mvar,valu)tin)let(envf:(Metavars_fuzzy.mvar*Parse_info.t,Ast_fuzzy.trees)matcher)=fun(mvar,tok)any->funtin->matchcheck_and_add_metavar_binding(mvar,any)tinwith|None->pr2(spf"envf: fail, %s"mvar);failtin|Somenew_binding->pr2(spf"envf: success, %s"mvar);return((mvar,tok),any)new_bindingend(*****************************************************************************)(* Entry point *)(*****************************************************************************)moduleMATCH=Fuzzy_vs_fuzzy.X_VS_X(XMATCH)type('a,'b)matcher='a->'b->Metavars_fuzzy.fuzzy_bindinglistlet(extract_bindings:'aXMATCH.tout->MV.fuzzy_bindinglist)=funtout->tout|>List.map(fun(_term,binding)->binding)letmatch_trees_treespatternx=letenv=MV.empty_environment()inMATCH.m_treespatternxenv|>extract_bindings