Commit | Line | Data |
---|---|---|
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 |
6 | module Ast = Ast_cocci |
7 | ||
8 | exception CompileFailure of string | |
9 | exception LinkFailure of string | |
10 | ||
feec80c3 | 11 | let ext = if Config.dynlink_is_native then ".cmxs" else ".cma" |
174d1640 | 12 | |
8babbc8f | 13 | let 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 | 19 | let 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 *) |
26 | let 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 | 32 | let 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 | ||
53 | let init_ocamlcocci _ = | |
54 | "open Coccilib\n" | |
55 | ||
aba5c457 C |
56 | let 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 | ||
64 | let 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 | ||
71 | let 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 |
110 | let 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 | ||
121 | let 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 " | |
152 | class 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 | 166 | let 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 | ||
192 | let 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 *) |
250 | let 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 | ||
263 | module ModuleSet = Set.Make(String) | |
264 | ||
265 | let 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 | ||
304 | let 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 | ||
334 | let 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 |
339 | let 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 | 383 | let 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 |
391 | let 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 |
395 | let 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 | |
400 | let compile mlfile cmd = | |
401 | Common.pr2 cmd; | |
402 | match Sys.command cmd with | |
403 | 0 -> () | |
404 | | _ -> raise (CompileFailure mlfile) | |
405 | ||
abad11c5 C |
406 | let 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 | |
413 | let load_lib (dir, name) = | |
414 | let obj = dir ^ "/" ^name ^ ext in | |
415 | Common.pr2 ("Loading "^ obj ^"..."); | |
416 | load_obj obj | |
417 | ||
418 | let load_libs libs = | |
419 | List.iter load_lib libs | |
420 | ||
174d1640 | 421 | let 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 | |
439 | let 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 | *) | |
460 | let test () = | |
461 | Hashtbl.iter | |
462 | (fun key fct -> | |
463 | Common.pr2 ("Fct registered: \""^key^"\"") | |
464 | ) Coccilib.fcts |