package pfff

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file spatch_fuzzy.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
(* 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.
 *)
open Common

module PI = Parse_info
open Parse_info
module V = 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 *)
(*****************************************************************************)

type pattern = Ast_fuzzy.trees

type line_kind = 
  | Context
  | Plus of string
  | 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
 *)
let parse 
  ~pattern_of_string
  ~ii_of_pattern
  file =
  let xs = Common.cat file +> Common.index_list_1 in

  let hline_env = Hashtbl.create 11 in

  let ys = xs +> List.map (fun (s, lineno) ->
    match s with
    (* 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.
     *)
    | _ when s =~ "^\\+[ \t]*\\(.*\\)" -> 
        let rest_line = Common.matched1 s in
        Hashtbl.add hline_env lineno (Plus rest_line);
        ""
    | _ when s =~ "^\\-\\(.*\\)" ->
        let rest_line = Common.matched1 s in
        Hashtbl.add hline_env lineno Minus;
        rest_line
    | _ ->
        Hashtbl.add hline_env lineno Context;
        s
  )
  in
  let spatch_without_patch_annot = Common2.unlines ys in
  (* pr2 spatch_without_patch_annot; *)

  let pattern = pattern_of_string spatch_without_patch_annot in

  (* need adjust the tokens in it now *)
  let toks = ii_of_pattern pattern in

  (* adjust with Minus info *)  
  toks +> List.iter (fun tok ->
    let line = PI.line_of_info tok in

    let annot = Hashtbl.find hline_env line in
    (match annot with
    | 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 ...
   *)

  let grouped_by_lines = 
    toks +> Common.group_by_mapped_key (fun tok -> PI.line_of_info tok) in
  let rec aux xs = 
    match xs with
    | (line, toks_at_line)::rest ->

        (* if the next line was a +, then associate with the last token
         * on this line
         *)
        (match Common2.hfind_option (line+1) hline_env with
        | None -> 
            (* probably because was last line *) 
            ()
        | Some (Plus toadd) ->
            (* todo? what if there is no token on this line ? *)
            let last_tok = Common2.list_last toks_at_line in

            (* ugly hack *)
            let toadd =
              match PI.str_of_info last_tok with
              | ";" -> "\n" ^ toadd
              | _ -> toadd
            in
              
            (match last_tok.PI.transfo with
            | Remove -> last_tok.PI.transfo <- Replace (AddStr toadd)
            | NoTransfo -> last_tok.PI.transfo <- AddAfter (AddStr toadd)
            | _ -> raise Impossible
            )
        | Some _ -> ()
        );
        aux rest

    | [] -> ()
  in
  aux grouped_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 *)
(*****************************************************************************)

let spatch pattern ast =

  let was_modifed = ref false in

  let len = List.length pattern in

  (* visit AST and try to match pattern on it *)
  let hook =
    { V.default_visitor with
      V.ktrees = (fun (k, _) xs ->
        if List.length xs >= len then begin
          let shorter, rest = Common2.splitAt len xs in

          (* pr (Ocaml.string_of_v (Ast_fuzzy.vof_trees shorter));*)

          let matches_with_env =
            Matching_fuzzy.match_trees_trees pattern shorter
          in
          if matches_with_env = []
          then
            (* recurse on sublists *)
            k xs
          else begin
            was_modifed := true;
            Transforming_fuzzy.transform_trees_trees pattern shorter
              (* TODO, maybe could get multiple matching env *)
              (List.hd matches_with_env);

            k rest
          end
        end
        else 
          (* at least recurse *)
          k xs
      );
    }
  in
  (V.mk_visitor hook) ast;
  !was_modifed


OCaml

Innovation. Community. Security.