123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233(* 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.
*)openCommonmodulePI=Parse_infoopenParse_infomoduleV=Lib_ast_fuzzy(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*
* See https://github.com/facebook/pfff/wiki/Spatch
*
* Here is an example of a spatch file:
*
* foo(2,
* - bar(2)
* + foobar(4)
* )
*
* This will replace all calls to bar(2) by foobar(4) when
* the function call is the second argument of a call to
* foo where its first argument is 2.
*
*
* note: can we produce syntactically incorrect code? Yes ...
*
* less: mostly copy paste of spatch_php.ml
*)(*****************************************************************************)(* Type *)(*****************************************************************************)typepattern=Ast_fuzzy.treestypeline_kind=|Context|Plusofstring|Minus(*****************************************************************************)(* Helpers *)(*****************************************************************************)(*****************************************************************************)(* Parsing *)(*****************************************************************************)(*
* Algorithm to parse a spatch file:
* - take lines of the file, index the lines
* - replace the + lines by an empty line and remember in a line_env
* the line and its index
* - remove the - in the first column and remember in a line_env
* that is was a minus line
* - unlines the filtered lines into a new string
* - call the parser on this new string
* - go through all tokens and adjust its transfo field using the
* information in line_env
*)letparse~pattern_of_string~ii_of_patternfile=letxs=Common.catfile|>Common.index_list_1inlethline_env=Hashtbl.create11inletys=xs|>List.map(fun(s,lineno)->matchswith(* ugly: for now I strip the space after the + because.
* at some point we need to parse this stuff and
* add the correct amount of indentation when it's processing
* a token.
*)|_whens=~"^\\+[ \t]*\\(.*\\)"->letrest_line=Common.matched1sinHashtbl.addhline_envlineno(Plusrest_line);""|_whens=~"^\\-\\(.*\\)"->letrest_line=Common.matched1sinHashtbl.addhline_envlinenoMinus;rest_line|_->Hashtbl.addhline_envlinenoContext;s)inletspatch_without_patch_annot=Common2.unlinesysin(* pr2 spatch_without_patch_annot; *)letpattern=pattern_of_stringspatch_without_patch_annotin(* need adjust the tokens in it now *)lettoks=ii_of_patternpatternin(* adjust with Minus info *)toks|>List.iter(funtok->letline=PI.line_of_infotokinletannot=Hashtbl.findhline_envlinein(matchannotwith|Context->()|Minus->tok.PI.transfo<-PI.Remove;|Plus_->(* normally impossible since we removed the elements in the
* plus line, except the newline. should assert it's only newline
*)()););(* adjust with the Plus info. We need to annotate the last token
* on the preceding line or next line.
* e.g. on
* foo(2,
* + 42,
* 3)
* we could either put the + on the ',' of the first line (as an AddAfter)
* or on the + on the '3' of the thirdt line (as an AddBefore).
* The preceding and next line could also be a minus line itself.
* Also it could be possible to have multiple + line in which
* case we want to concatenate them together.
*
* TODO: for now I just associate it with the previous line ...
* what if the spatch is:
* + foo();
* bar();
* then there is no previous line ...
*)letgrouped_by_lines=toks|>Common.group_by_mapped_key(funtok->PI.line_of_infotok)inletrecauxxs=matchxswith|(line,toks_at_line)::rest->(* if the next line was a +, then associate with the last token
* on this line
*)(matchCommon2.hfind_option(line+1)hline_envwith|None->(* probably because was last line *)()|Some(Plustoadd)->(* todo? what if there is no token on this line ? *)letlast_tok=Common2.list_lasttoks_at_linein(* ugly hack *)lettoadd=matchPI.str_of_infolast_tokwith|";"->"\n"^toadd|_->toaddin(matchlast_tok.PI.transfowith|Remove->last_tok.PI.transfo<-Replace(AddStrtoadd)|NoTransfo->last_tok.PI.transfo<-AddAfter(AddStrtoadd)|_->raiseImpossible)|Some_->());auxrest|[]->()inauxgrouped_by_lines;(* both the ast (here pattern) and the tokens share the same
* reference so by modifying the tokens we actually also modifed
* the AST.
*)pattern(*****************************************************************************)(* Main entry points *)(*****************************************************************************)letspatchpatternast=letwas_modifed=reffalseinletlen=List.lengthpatternin(* visit AST and try to match pattern on it *)lethook={V.default_visitorwithV.ktrees=(fun(k,_)xs->ifList.lengthxs>=lenthenbeginletshorter,rest=Common2.splitAtlenxsin(* pr (Ocaml.string_of_v (Ast_fuzzy.vof_trees shorter));*)letmatches_with_env=Matching_fuzzy.match_trees_treespatternshorterinifmatches_with_env=[]then(* recurse on sublists *)kxselsebeginwas_modifed:=true;Transforming_fuzzy.transform_trees_treespatternshorter(* TODO, maybe could get multiple matching env *)(List.hdmatches_with_env);krestendendelse(* at least recurse *)kxs);}in(V.mk_visitorhook)ast;!was_modifed