permit multiline comments and strings in macros
[bpt/coccinelle.git] / ocaml / yes_prepare_ocamlcocci.ml
CommitLineData
abad11c5
C
1(* Note: this module passes paths to other commands, but does not take
2 * quoting into account. Thus, if these paths contain spaces, it's likely
3 * that things go wrong.
4 *)
5
174d1640
C
6module Ast = Ast_cocci
7
8exception CompileFailure of string
9exception LinkFailure of string
10
feec80c3 11let ext = if Config.dynlink_is_native then ".cmxs" else ".cma"
174d1640 12
8babbc8f 13let sysdir () =
1eddfd50
C
14 let sysdircmd = !Flag.ocamlfind ^ " printconf stdlib" in
15 match Common.cmd_to_list sysdircmd with
16 [sysdir] -> sysdir
17 | _ -> raise (CompileFailure (sysdircmd ^" has failed"))
18
174d1640 19let check_cmd cmd =
c491d8ee
C
20 let (_,stat) = Common.cmd_to_list_and_status cmd in
21 match stat with
22 Unix.WEXITED 0 -> true
174d1640
C
23 | _ -> false
24
feec80c3
C
25(* this function does not work when the executable has an extension like .exe *)
26let to_opt cmd =
27 let n = String.length cmd in
28 if n > 4 && String.compare (String.sub cmd (n-4) 4) ".opt" == 0
29 then cmd
30 else cmd ^ ".opt"
31
174d1640 32let check_runtime () =
feec80c3 33 let has_opt = check_cmd (to_opt (!Flag.ocamlc) ^ " -version 2>&1 > /dev/null") in
d6ce1786 34 let has_c = check_cmd (!Flag.ocamlc ^ " -version 2>&1 > /dev/null") in
174d1640
C
35 if has_opt then
36 begin
feec80c3
C
37 Flag.ocamlc := to_opt (!Flag.ocamlc);
38 Flag.ocamlopt := to_opt (!Flag.ocamlopt);
39 Flag.ocamldep := to_opt (!Flag.ocamldep);
174d1640
C
40 Common.pr2 "Using native version of ocamlc/ocamlopt/ocamldep"
41 end
42 else
43 if has_c then
44 Common.pr2 "Using bytecode version of ocamlc/ocamlopt/ocamldep"
45 else
feec80c3 46 if Config.dynlink_is_native then
174d1640
C
47 failwith
48 "No OCaml compiler found! Install either ocamlopt or ocamlopt.opt"
49 else
50 failwith
51 "No OCaml compiler found! Install either ocamlc or ocamlc.opt"
52
53let init_ocamlcocci _ =
54 "open Coccilib\n"
55
aba5c457
C
56let print_match ctr nm kind =
57 let endlet = "| _ -> failwith \"bad value\" in\n" in
58 let index = !ctr in
59 ctr := !ctr + 1;
60 Printf.sprintf
61 "let %s = match List.nth args %d with Coccilib.%s x -> x %s"
62 nm index kind endlet
63
64let string_rep_binding ctr = function
65 (Some nm,Ast.MetaPosDecl _) -> print_match ctr nm "Pos"
66 | (Some nm,Ast.MetaListlenDecl _) -> print_match ctr nm "Int"
67 | (Some nm,_) (* strings for everything else *) ->
68 print_match ctr nm "Str"
69 | (None,_) -> ""
70
71let ast_rep_binding ctr = function
72 (Some nm,Ast.MetaPosDecl _) ->
73 failwith
74 (Printf.sprintf "%s: No AST representation for position variables" nm)
1b9ae606
C
75 | (Some nm,Ast.MetaAnalysisDecl _) ->
76 failwith "Todo"
77
b23ff9c7
C
78 | (Some nm,Ast.MetaMetaDecl _) ->
79 failwith
80 (Printf.sprintf
81 "%s: No AST representation for metavariables declared as \"%s\""
82 "metavariable" nm)
aba5c457
C
83 | (Some nm,Ast.MetaIdDecl _) -> print_match ctr nm "Str"
84 | (Some nm,Ast.MetaFreshIdDecl _) -> print_match ctr nm "Str"
85 | (Some nm,Ast.MetaTypeDecl _) -> print_match ctr nm "Type"
86 | (Some nm,Ast.MetaInitDecl _) -> print_match ctr nm "Init"
8f657093 87 | (Some nm,Ast.MetaInitListDecl _) -> print_match ctr nm "InitList"
aba5c457
C
88 | (Some nm,Ast.MetaListlenDecl _) ->
89 failwith
90 (Printf.sprintf "%s: No AST representation for listlen variables" nm)
91 | (Some nm,Ast.MetaParamDecl _) -> print_match ctr nm "Param"
92 | (Some nm,Ast.MetaParamListDecl _) -> print_match ctr nm "ParamList"
93 | (Some nm,Ast.MetaConstDecl _) -> print_match ctr nm "Expr"
94 | (Some nm,Ast.MetaErrDecl _) -> failwith ("not supported: "^nm)
95 | (Some nm,Ast.MetaExpDecl _) -> print_match ctr nm "Expr"
96 | (Some nm,Ast.MetaIdExpDecl _) -> print_match ctr nm "Expr"
97 | (Some nm,Ast.MetaLocalIdExpDecl _) -> print_match ctr nm "Expr"
98 | (Some nm,Ast.MetaExpListDecl _) -> print_match ctr nm "ExprList"
413ffc02
C
99 | (Some nm,Ast.MetaDeclDecl _) -> print_match ctr nm "Decl"
100 | (Some nm,Ast.MetaFieldDecl _) -> print_match ctr nm "Field"
190f1acf 101 | (Some nm,Ast.MetaFieldListDecl _) -> print_match ctr nm "FieldList"
aba5c457
C
102 | (Some nm,Ast.MetaStmDecl _) -> print_match ctr nm "Stmt"
103 | (Some nm,Ast.MetaStmListDecl _) -> failwith ("not supported: "^nm)
104 | (Some nm,Ast.MetaFuncDecl _) -> print_match ctr nm "Str"
105 | (Some nm,Ast.MetaLocalFuncDecl _) -> print_match ctr nm "Str"
106 | (Some nm,Ast.MetaDeclarerDecl _) -> print_match ctr nm "Str"
107 | (Some nm,Ast.MetaIteratorDecl _) -> print_match ctr nm "Str"
108 | (None,_) -> ""
109
413ffc02
C
110let manage_script_vars script_vars =
111 let rec loop n = function
112 [] -> ""
113 | (_,x)::xs ->
114 (Printf.sprintf "let %s = List.nth script_args %d in\n" x n) ^
115 (loop (n+1) xs) in
116 loop 0 script_vars
117
3a314143
C
118(* ---------------------------------------------------------------------- *)
119(* Iteration management *)
120
121let print_iteration_code o =
122 let translator l =
123 String.concat "\n | "
124 (List.map
125 (function x -> Printf.sprintf "%s -> \"%s\""
126 (String.capitalize x) x)
127 l) in
128 let add_virt_rules_method =
129 match !Iteration.parsed_virtual_rules with
130 [] -> ""
131 | l ->
132 Printf.sprintf "
133 method add_virtual_rule r =
134 let r = match r with %s in
135 virtual_rules <- Common.union_set [r] virtual_rules\n"
136 (translator l) in
137 let add_virt_ids_method =
138 match !Iteration.parsed_virtual_identifiers with
139 [] -> ""
140 | l ->
141 Printf.sprintf "
142 method add_virtual_identifier i v =
143 let i = match i with %s in
144 try
145 let v1 = List.assoc i virtual_identifiers in
146 if not (v = v1)
147 then failwith (\"multiple values specified for \"^i)
148 with Not_found ->
149 virtual_identifiers <- (i,v) :: virtual_identifiers"
150 (translator l) in
151 Printf.fprintf o "
152class iteration () =
153 object
154 val mutable files = None
155 val mutable files_changed = false
156 val mutable virtual_rules = ([] : string list)
157 val mutable virtual_identifiers = ([] : (string * string) list)
158 method set_files f = files <- Some f
159 %s%s
160 method register () =
161 Iteration.add_pending_instance (files,virtual_rules,virtual_identifiers)
162 end\n\n" add_virt_rules_method add_virt_ids_method
163
164(* ---------------------------------------------------------------------- *)
165
413ffc02 166let prepare_rule (name, metavars, script_vars, code) =
174d1640
C
167 let fname = String.concat "_" (Str.split (Str.regexp " ") name) in
168 (* function header *)
169 let function_header body =
413ffc02 170 Printf.sprintf "let %s args script_args =\n %s" fname body in
174d1640
C
171 (* parameter list *)
172 let build_parameter_list body =
173 let ctr = ref 0 in
aba5c457
C
174 let lets =
175 String.concat ""
176 (List.rev
177 (List.fold_left
178 (function prev ->
179 function ((str_nm,ast_nm),_,mv) ->
180 (* order important; ctr is incremented *)
181 let string_rep = string_rep_binding ctr (str_nm,mv) in
182 let ast_rep = ast_rep_binding ctr (ast_nm,mv) in
183 ast_rep :: string_rep :: prev)
184 [] metavars)) in
413ffc02 185 lets ^ (manage_script_vars script_vars) ^ body in
174d1640
C
186 (* add to hash table *)
187 let hash_add body =
188 Printf.sprintf
189 "%s\nlet _ = Hashtbl.add Coccilib.fcts \"%s\" %s\n" body name fname in
190 hash_add (function_header (build_parameter_list code))
191
192let prepare coccifile code =
193 let init_rules =
194 List.fold_left
195 (function prev ->
196 function
197 Ast_cocci.InitialScriptRule (name,"ocaml",deps,code) ->
198 code :: prev
199 | _ -> prev)
200 [] code in
201 let init_rules = List.rev init_rules in
202 let other_rules =
203 List.fold_left
204 (function prev ->
205 function
413ffc02
C
206 Ast_cocci.ScriptRule (name,"ocaml",deps,mv,script_vars,code) ->
207 (name,mv,script_vars,code) :: prev
174d1640
C
208 | Ast_cocci.InitialScriptRule (name,"ocaml",deps,code) -> prev
209 | Ast_cocci.FinalScriptRule (name,"ocaml",deps,code) ->
413ffc02 210 (name,[],[],code) :: prev
174d1640
C
211 | _ -> prev)
212 [] code in
213 let other_rules = List.rev other_rules in
214 if init_rules = [] && other_rules = []
215 then None
216 else
3a314143
C
217 begin
218 let basefile = Filename.basename (Filename.chop_extension coccifile) in
219 let basefile =
220 String.concat "_" (Str.split (Str.regexp "-") basefile) in
221 let (file,o) = Filename.open_temp_file basefile ".ml" in
174d1640 222 (* Global initialization *)
3a314143
C
223 Printf.fprintf o "%s\n" (init_ocamlcocci());
224 (* virtual rules and identifiers *)
225 (if !Iteration.parsed_virtual_rules != []
226 then
227 Printf.fprintf o "type __virtual_rules__ = %s\n\n"
228 (String.concat " | "
229 (List.map String.capitalize !Iteration.parsed_virtual_rules)));
230 (if !Iteration.parsed_virtual_identifiers != []
231 then
232 Printf.fprintf o "type __virtual_identifiers__ = %s\n\n"
233 (String.concat " | "
234 (List.map
235 (function x -> Printf.sprintf "%s" x)
236 (List.map String.capitalize
237 !Iteration.parsed_virtual_identifiers))));
238 print_iteration_code o;
174d1640
C
239 (* Semantic patch specific initialization *)
240 Printf.fprintf o "%s" (String.concat "\n\n" init_rules);
241 (* Semantic patch rules and finalizer *)
242 let rule_code = List.map prepare_rule other_rules in
3a314143
C
243 Printf.fprintf o "%s" (String.concat "\n\n" rule_code);
244 close_out o;
245 check_runtime ();
246 Some file
247 end
174d1640 248
abad11c5
C
249(* give a path to the coccilib cmi file *)
250let find_cmifile name =
251 let path1 = Printf.sprintf "%s/ocaml/%s.cmi" Config.path name in
252 if Sys.file_exists path1 then path1 else
253 let path2 = Printf.sprintf "%s/ocaml/coccilib/%s.cmi" Config.path name in
254 if Sys.file_exists path2 then path2 else
255 raise (CompileFailure ("No coccilib.cmi in " ^ path1 ^ " or " ^ path2))
256
257(* extract upper case identifiers from the cmi file. This will be an
258 * approximation of the modules referenced by the coccilib, which are
259 * thus present in the application and do not need to be loaded by
260 * the dynamic linker.
261 *)
262
263module ModuleSet = Set.Make(String)
264
265let approx_coccilib_deps cmi =
266 let chan = open_in_bin cmi in
267 let tbl = Hashtbl.create 1024 in
268 let buf = Buffer.create 140 in
269 begin
270 try
271 while true do
272 let c = input_char chan in
273 let has_ident = Buffer.length buf > 0 in
274 if has_ident
275 then begin
276 if (c >= 'a' && c <= 'z') ||
277 (c >= 'A' && c <= 'Z') ||
278 (c >= '0' && c <= '9') ||
279 c == '_' || c == '\''
280 then Buffer.add_char buf c
281 else begin
282 if Buffer.length buf >= 3
283 then begin
284 let key = Buffer.contents buf in
285 if Hashtbl.mem tbl key
286 then ()
287 else Hashtbl.add tbl (Buffer.contents buf) ()
288 end;
289 Buffer.clear buf
290 end
291 end
292 else begin
293 if c >= 'A' && c <= 'Z'
294 then (* perhaps the begin of a captialized identifier *)
295 Buffer.add_char buf c
296 else ()
297 end
298 done
299 with End_of_file -> ()
300 end;
301 close_in chan;
302 tbl
303
304let filter_dep existing_deps (accld, accinc) dep =
305 if Hashtbl.mem existing_deps dep
306 then (accld, accinc) (* skip an existing dep *)
307 else match dep with
174d1640
C
308 (* Built-in and OCaml defaults are filtered out *)
309 "Arg" | "Arith_status" | "Array" | "ArrayLabels" | "Big_int" | "Bigarray"
aba5c457
C
310 | "Buffer" | "Callback" | "CamlinternalLazy" | "CamlinternalMod"
311 | "CamlinternalOO"
312 | "Char" | "Complex" | "Condition" | "Digest" | "Dynlink" | "Event"
313 | "Filename"
314 | "Format" | "Gc" | "Genlex" | "GraphicsX11" | "Hashtbl" | "Int32"
315 | "Int64"
316 | "Lazy" | "Lexing" | "List" | "ListLabels" | "Map" | "Marshal"
317 | "MoreLabels" | "Mutex"
318 | "Nativeint" | "Num" | "Obj" | "Oo" | "Parsing" | "Pervasives"
319 | "Printexc" | "Printf"
320 | "Queue" | "Random" | "Scanf" | "Set" | "Sort" | "Stack" | "StdLabels"
321 | "Str" | "Stream"
322 | "String" | "StringLabels" | "Sys" | "ThreadUnix" | "Unix" | "UnixLabels"
abad11c5 323 | "Weak" -> (accld, accinc)
aba5c457
C
324 | "Dbm" -> ("dbm"::accld, accinc)
325 | "Graphics" -> ("graphics"::accld, accinc)
326 | "Thread" -> ("thread"::accld, accinc)
327 | "Tk" -> ("tk"::accld, accinc)
174d1640
C
328
329 | _ ->
330 let l = Char.lowercase (String.get dep 0)in
331 String.set dep 0 l;
aba5c457
C
332 (accld, dep::accinc)
333
334let get_dir p =
335 let inclcmd = !Flag.ocamlfind ^" query "^p in
336 let dir = List.hd (Common.cmd_to_list inclcmd) in
337 (dir, p)
174d1640 338
abad11c5
C
339let parse_dep cmifile mlfile depout =
340 let empty_deps = ([], "") in
341 let existing_deps = approx_coccilib_deps cmifile in
174d1640
C
342 let re_colon = Str.regexp_string ":" in
343 match Str.split re_colon depout with
aba5c457
C
344 _::[dep] ->
345 let deplist = Str.split (Str.regexp_string " ") dep in
abad11c5 346 let (libs, orderdep) = List.fold_left (filter_dep existing_deps) ([],[]) deplist in
aba5c457 347 if libs <> [] || orderdep <> [] then
ca417fcf
C
348 begin
349 if check_cmd (!Flag.ocamlfind ^ " printconf 2>&1 > /dev/null")
aba5c457 350 then
ca417fcf
C
351 let packages = List.rev orderdep in
352 let inclflags = List.map get_dir packages in
353 let intlib = List.map get_dir libs in
354 let alllibs = List.rev_append intlib inclflags in
355 let plist =
356 List.fold_left (fun acc (_,p) -> acc ^" "^p) "" alllibs in
357 let flags =
358 String.concat " " (List.map (fun (d,_) -> "-I "^d) inclflags) in
359 if flags <> "" || libs <> []
abad11c5
C
360 then begin
361 Common.pr2
362 ("Extra OCaml packages used in the semantic patch:"^ plist);
363 (alllibs, flags)
364 end
365 else begin
366 Common.pr2 ("Warning: ocamlfind did not find "^
ca417fcf
C
367 (if (List.length libs + List.length orderdep) = 1
368 then "this package:"
abad11c5
C
369 else "one of these packages:")^ plist);
370 empty_deps
371 end
372 else begin
373 Common.pr2 ("Warning: ocamlfind not found but "^mlfile^" uses "^dep);
374 empty_deps
375 end
ca417fcf 376 end
aba5c457 377 else
abad11c5 378 empty_deps
aba5c457
C
379 | _ ->
380 raise
381 (CompileFailure ("Wrong dependencies for "^mlfile^" (Got "^depout^")"))
413ffc02 382
abad11c5 383let dep_flag cmifile mlfile =
174d1640 384 let depcmd = !Flag.ocamldep ^" -modules "^mlfile in
aba5c457 385 match Common.cmd_to_list depcmd with
abad11c5 386 [dep] -> parse_dep cmifile mlfile dep
1eddfd50
C
387 | err ->
388 List.iter (function x -> Common.pr2 (x^"\n")) err;
389 raise (CompileFailure ("Failed ocamldep for "^mlfile))
413ffc02 390
174d1640
C
391let compile_bytecode_cmd flags mlfile =
392 let obj = (Filename.chop_extension mlfile) ^ ".cmo" in
aba5c457 393 (obj, Printf.sprintf "%s -c %s %s %s" !Flag.ocamlc obj flags mlfile)
413ffc02 394
174d1640
C
395let compile_native_cmd flags mlfile =
396 let obj = (Filename.chop_extension mlfile) ^ ".cmxs" in
aba5c457
C
397 (obj,
398 Printf.sprintf "%s -shared -o %s %s %s" !Flag.ocamlopt obj flags mlfile)
174d1640
C
399
400let compile mlfile cmd =
401 Common.pr2 cmd;
402 match Sys.command cmd with
403 0 -> ()
404 | _ -> raise (CompileFailure mlfile)
405
abad11c5
C
406let load_obj obj =
407 Dynlink.allow_unsafe_modules true;
408 try Dynlink.loadfile obj
aba5c457 409 with Dynlink.Error e ->
abad11c5
C
410 Common.pr2 (Dynlink.error_message e);
411 raise (LinkFailure obj)
aba5c457
C
412
413let load_lib (dir, name) =
414 let obj = dir ^ "/" ^name ^ ext in
415 Common.pr2 ("Loading "^ obj ^"...");
416 load_obj obj
417
418let load_libs libs =
419 List.iter load_lib libs
420
174d1640 421let load_file mlfile =
abad11c5
C
422 let cmifile = find_cmifile "coccilib" in
423 let (ldlibs, inc) = dep_flag cmifile mlfile in
424 (* add ocaml and ocaml/coccilib as search directories for the ocaml scripting *)
aba5c457
C
425 let flags =
426 Printf.sprintf
abad11c5
C
427 "-g -I %s %s -I %s"
428 (sysdir ()) inc (Filename.dirname cmifile) in
174d1640 429 let (obj, cmd) =
feec80c3 430 if Config.dynlink_is_native
174d1640 431 then compile_native_cmd flags mlfile
aba5c457
C
432 else compile_bytecode_cmd flags mlfile in
433 compile mlfile cmd;
434 Common.pr2 "Compilation OK!";
435 load_libs ldlibs;
436 Common.pr2 "Loading ML code of the SP...";
abad11c5 437 load_obj obj
174d1640
C
438
439let clean_file mlfile =
440 let basefile = Filename.chop_extension mlfile in
441 let files =
feec80c3 442 if Config.dynlink_is_native then
174d1640
C
443 [basefile ^ ".cmxs";
444 basefile ^ ".cmx";
aba5c457
C
445 basefile ^ ".o";
446 basefile ^ ".annot"]
174d1640 447 else
aba5c457
C
448 [basefile ^ ".cmo";
449 basefile ^ ".annot"]
174d1640 450 in
993936c0 451 if not !Flag_parsing_cocci.keep_ml_script then Sys.remove mlfile;
174d1640 452 Sys.remove (basefile^".cmi");
aba5c457 453 List.iter (fun f -> try Sys.remove f with _ -> ()) files
174d1640
C
454
455(*
456 This function is used in testing.ml.
457 Once the ML file is compiled and loaded,
458 newly available functions are reported here.
459*)
460let test () =
461 Hashtbl.iter
462 (fun key fct ->
463 Common.pr2 ("Fct registered: \""^key^"\"")
464 ) Coccilib.fcts