Source

cmigrep-fork / cmigrep.ml

Full commit
  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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
(* A utility to gather information from caml compiled interface files

   Copyright (C) 2007 Eric Stokes

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2.1 of
   the License, or (at your option) any later version.
   
   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 GNU           
   Lesser General Public License for more details.                             
   
   You should have received a copy of the GNU General Public License
   along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA
*)

open Types

exception Break

(* the standard library should not be so deficient *)
module List = struct
  include List

  let filter_map f l = 
    List.fold_left
      (fun acc item -> 
         match f item with
         | Some x -> x :: acc
         | None -> acc)
      []
      l

  let find_map f l = 
    let res = ref None in
    try
      List.iter
        (fun x -> 
           match f x with
           | None -> ()
           | Some y -> res := Some y; raise Break)
        l;
      raise Not_found
    with Break -> 
      begin match !res with
      | Some y -> y
      | None -> raise Break
      end

  let map f l = 
    let r = rev_map f l in
    rev r
end

module Unix = struct
  include Unix

  let fold_path ~f ~init path = 
    let dir = Unix.opendir path in
    let acc = ref init in
    try
      while true do
        let file = Unix.readdir dir in
        acc := f file !acc
      done;
      !acc
    with 
    | End_of_file -> Unix.closedir dir; !acc
    | exn -> Unix.closedir dir; raise exn
end

module Match = struct
  let comma = Pcre.regexp "\\s*,\\s*"
end

module Module_expression = struct
  type t = 
  | Exact of string
  | Begins_with of string * Pcre.regexp
  | Ends_with of string * Pcre.regexp
  | Begins_and_ends of string * Pcre.regexp
  | Contains of string * Pcre.regexp
  | Any

  let to_string = function
    | Exact m -> m
    | Begins_with (s, _) 
    | Ends_with (s, _)
    | Begins_and_ends (s, _)
    | Contains (s, _) -> s
    | Any -> "*"

  (* ModA,ModB,Foo*.Make *)
  let parse = 
    let dot = Pcre.regexp "\\." in
    let capname = Pcre.regexp "^[A-Z][A-Za-z_'0-9]*$" in
    let starend = Pcre.regexp "^[A-Z][A-Za-z0-9_']*\\*$" in
    let starbegin = Pcre.regexp "^\\*[A-Za-z0-9_']+$" in
    let starboth = Pcre.regexp "^\\*[A-Za-z0-9_']+\\*$" in
    let starmiddle = Pcre.regexp "^([A-Z][A-Za-z0-9_']*)\\*([A-Za-z0-9_']+)$" in
    let star = Pcre.regexp "\\*" in
    fun exp ->
      List.map
        (fun token ->
          let token_no_star = Pcre.replace ~rex:star ~templ:"" token in
          if token = "*" then
            Any
          else if Pcre.pmatch ~rex:capname token then
            Exact token
          else if Pcre.pmatch ~rex:starboth token then
            Contains (token, Pcre.regexp ("^.*" ^ token_no_star ^ ".*$"))
          else if Pcre.pmatch ~rex:starmiddle token then
            begin match Pcre.extract ~rex:starmiddle token with
            | [|_whole; begins; ends |] -> 
                let rex = Pcre.regexp (Printf.sprintf "^%s.*%s$" begins ends) in
                Begins_and_ends (token, rex)
            | _ -> failwith "invalid begins and ends with match"
            end
          else if Pcre.pmatch ~rex:starbegin token then
            Ends_with (token, Pcre.regexp (Printf.sprintf "%s$" token_no_star))
          else if Pcre.pmatch ~rex:starend token then
            Begins_with (token, Pcre.regexp (Printf.sprintf "^%s" token_no_star))
          else
            failwith "invalid module expression")
        (Pcre.split ~rex:dot exp)

  let is_exact t = 
    List.for_all 
      (function 
        | Exact _ -> true 
        | Begins_with _
        | Ends_with _
        | Begins_and_ends _
        | Contains _
        | Any -> false)
      t

  let parse_exact exp = 
    let t = parse exp in
    if is_exact t then
      t
    else
      failwith "Module_expression.parse_exact: expression is not exact!"
end

type mode = 
  | Find_type of Pcre.regexp
  | Find_constructor of Pcre.regexp
  | Find_polymorphic_variant of Pcre.regexp
  | Find_record_label of Pcre.regexp
  | Find_value of Pcre.regexp
  | Find_exception of Pcre.regexp
  | Find_module
  | Find_class of Pcre.regexp
  | Find_all of Pcre.regexp

type module_tree = 
  | Leaf of string * signature
  | Node of string * signature * module_tree list

type args = {
  mode: mode;
  path: string list;
  context: Module_expression.t list list; (* open modules *)
  modname: Module_expression.t list list;
}

let parse_args () =
  let module Parse = struct
    open Arg
    let mode            = ref None
    let default_path    = ref [Config.standard_library] (* the default load path *)
    let default_context = ref ["Pervasives"] (* the default opened modules *)
    let path            = ref ["."] (* the load path *)
    let context         = ref [] (* the opened modules *)
    let modname         = ref []

    let set_mode m =
      match !mode with
      | None -> mode := Some m;
      | Some _ -> raise (Invalid_argument "the mode is already set")

    let add_packages p = 
      Findlib.init ();
      let packages = Pcre.split ~rex:Match.comma p in
      List.iter 
        (fun package ->
          try
            let dir = Findlib.package_directory package in
            path := dir::!path
          with exn -> 
            Printf.eprintf "warning, error finding package dir: %s\n" (Printexc.to_string exn))
        packages

    let add_opens s = context := Pcre.split ~rex:Match.comma s

    let args = 
      Arg.align
        [("-t", String (fun s -> set_mode (Find_type (Pcre.regexp s))),
          "      (regexp) print types with matching names");
         ("-r", String (fun s -> set_mode (Find_record_label (Pcre.regexp s))),
          "      (regexp) print record field labels with matching names");
         ("-c", String (fun s -> set_mode (Find_constructor (Pcre.regexp s))), 
          "      (regexp) print constructors with matching names");
         ("-p", String (fun s -> 
                         set_mode 
                           (Find_polymorphic_variant 
                             (Pcre.regexp s))),
          "      (regexp) print polymorphic variants with matching names");
         ("-m", Unit (fun () -> set_mode Find_module),
          "      (regexp) print all matching module names in the path");
         ("-v", String (fun s -> set_mode (Find_value (Pcre.regexp s))), 
          "      (regexp) print values with matching names");
         ("-e", String (fun s -> set_mode (Find_exception (Pcre.regexp s))), 
          "      (regexp) print exceptions with matching constructors");
         ("-o", String (fun s -> set_mode (Find_class (Pcre.regexp s))),
          "      (regexp) print all classes with matching names");
         ("-a", String (fun s -> set_mode (Find_all (Pcre.regexp s))),
          "      (regexp) print all names which match the given expression");
         ("-I", String (fun s -> path := s::!path), 
          "      (directory) add additional directory to the search path");
         ("-package", String (fun s -> add_packages s),
          "      (packages) comma seperated list of findlib packages to search");
         ("-open", String (fun s -> add_opens s), 
          "      (modules) comma seperated list of open modules (in order!)");

         ("-nostdlib", Unit (fun () -> default_path := []),
          "      Do not add default directory to the list of include directories");
         ("-nopervasives", Unit (fun () -> default_context := []),
          "      make Pervasives not opened");
        ]
    let usage = 
      Printf.sprintf
        ("%s: <option> <mode-switches> <args> <module-expr> \n" ^^
           "extract information from caml compiled interface files\n" ^^
           " <module-expr> can be an exact module name, " ^^
           " or a shell wildcard. Multiple modules can be specified " ^^
           "E.G. \"ModA ModB Foo*.Make\" means to search ModA, ModB, and " ^^
           "any submodule Make of a module that starts with Foo.")
        Sys.argv.(0)

    let parse () = 
      Arg.parse args
        (fun anon -> modname := (Module_expression.parse anon) :: !modname)
        usage

    let error msg = 
      prerr_endline msg;
      Arg.usage args usage;
      exit 1
  end
  in
  Parse.parse ();
  let mode = 
    match !Parse.mode with
    | Some m -> m
    | None -> Parse.error "you must specify a search mode"
  in

  (* Merge the default and user supplied path and context *)
  let path = !Parse.path @ !Parse.default_path in
  let context = !Parse.context @ !Parse.default_context in

  {mode = mode;
   path = 
      if path = [] then Parse.error "you must specify a search path"
      else path;
   context = 
      List.map
        Module_expression.parse_exact
        context;
   modname = 
      (match !Parse.modname with
       | [] -> 
           if !Parse.context = [] then
             Parse.error "you must specify a module expression, or a list of open modules"
           else 
             []
       | name -> name)}

let match_ident exp id = Pcre.pmatch ~rex:exp (Ident.name id)

let whsp = Pcre.regexp ~study:true "\\s+|$"

let print_type print_path path s exp =
  List.iter
    (function 
       | Tsig_type (id, type_decl, rec_status) ->
           if match_ident exp id then begin
             Printtyp.type_declaration id Format.std_formatter type_decl;
             if print_path then
               Format.print_string (Printf.sprintf " (* %s *)" path);
             Format.print_newline ()
           end
       | _ -> ())
    s

let print_constructor print_path path s exp =
  let type_expr_to_string exp = 
    Printtyp.type_expr Format.str_formatter exp;
    Format.flush_str_formatter ();
  in
  List.iter
    (function
       | Tsig_type (id, type_decl, _rec_status) ->
           begin match type_decl.type_kind with
           | Type_variant constructors ->
               List.iter
                 (fun (name, type_exprs) ->
                    if Pcre.pmatch ~rex:exp name then begin
                      Format.print_string name;
                      if type_exprs <> [] then begin
                        Format.print_string " of ";
                        Format.print_string
                          (String.concat " * "
                             (List.map
                                (fun e -> type_expr_to_string e)
                                type_exprs))
                      end;
                      Format.print_string " (* ";
                      if print_path then
                        Format.print_string (path ^ ".");
                      Format.print_string (Ident.name id);
                      Format.print_string " *)";
                      Format.print_newline ()
                    end)
                 constructors
           | _ -> ()
           end
       | _ -> ())
    s

let print_polymorphic_variant print_path path s expr = 
  let print_if_polymorphic_variant id type_decl =
    begin match type_decl.type_manifest with
    | None -> ()
    | Some {desc = type_descr} ->
        begin match type_descr with
        | Tvariant variant_descr ->
            List.iter
              (fun (name, param) ->
                 let src_name = "`" ^ name in
                 if Pcre.pmatch ~rex:expr src_name then begin
                   Format.print_string src_name;
                   begin match param with
                   | Rpresent None -> ()
                   | Rabsent -> ()
                   | Reither _ -> () (* this can't happen in a type *)
                   | Rpresent (Some type_expr) ->
                       Format.print_string " of ";
                       Printtyp.type_expr 
                         Format.str_formatter type_expr;
                       let s = 
                         Pcre.replace ~rex:whsp ~templ:" "
                           (Format.flush_str_formatter ()) 
                       in
                       Format.print_string s;
                   end;
                   Format.print_string 
                     (Printf.sprintf " (* %s%s *)"
                        (if print_path then (path ^ ".") else "")
                        (Ident.name id));
                   Format.print_newline ()
                 end)
              variant_descr.row_fields
        | _ -> ()
        end
    end
  in
  List.iter
    (function 
       | Tsig_type (id, type_decl, _rec_status) ->
           begin match type_decl.type_kind with
           | Type_abstract -> print_if_polymorphic_variant id type_decl
           | _ -> ()
           end
       | _ -> ())
    s

let print_record_label print_path path s exp =
  List.iter
    (function
       | Tsig_type (id, type_decl, _rec_status) ->
           begin match type_decl.type_kind with
           | Type_record (labels, _) ->
               List.iter
                 (fun (name, mutable_flag, type_expr) ->
                    if Pcre.pmatch ~rex:exp name then begin
                      begin match mutable_flag with
                      | Asttypes.Mutable -> Format.print_string "mutable "
                      | Asttypes.Immutable -> ()
                      end;
                      Format.print_string name;
                      Format.print_string ": ";
                      Printtyp.type_expr Format.std_formatter type_expr;
                      Format.print_string " (* ";
                      if print_path then
                        Format.print_string (path ^ ".");
                      Format.print_string (Ident.name id);
                      Format.print_string " *)";
                      Format.print_newline ()
                    end)
                 labels
           | _ -> ()
           end
       | _ -> ())
    s

let print_value print_path path s exp =
  List.iter
    (function
       | Tsig_value (id, desc) ->
           if match_ident exp id then begin
             Printtyp.value_description id Format.str_formatter desc;
             let s = 
               Pcre.replace ~rex:whsp ~templ:" "
                 (Format.flush_str_formatter ()) 
             in
             if print_path then
               print_endline (s ^ (Printf.sprintf " (* %s *)" path))
             else
               print_endline s
           end
       | _ -> ())
    s

let print_class print_path path s exp =
  List.iter
    (function
       | Tsig_class (id, cd, _) when match_ident exp id ->
           Printtyp.class_declaration id Format.std_formatter cd;
           if print_path then
             Format.print_string (Printf.sprintf " (* %s *)" path);
           Format.print_newline ()
       | Tsig_cltype (id, ct, _) when match_ident exp id ->
           Printtyp.cltype_declaration id Format.std_formatter ct;
           if print_path then
             Format.print_string (Printf.sprintf " (* %s *)" path);
           Format.print_newline ()
       | _ -> ())
    s

let print_all print_path path s exp = 
  let new_s = 
    List.filter 
      (function
         | Tsig_value (id, _)
         | Tsig_type (id, _, _)
         | Tsig_exception (id, _)
         | Tsig_module (id, _, _)
         | Tsig_modtype (id, _)
         | Tsig_class (id, _, _)
         | Tsig_cltype (id, _, _) ->
             match_ident exp id)
      s
  in
  Printtyp.signature Format.std_formatter new_s;
  if print_path then Format.printf " (* %s *)" path;
  Format.print_newline ()

let print_exception print_path path s exp = 
  List.iter
    (function 
       | Tsig_exception (id, exn) ->
           if match_ident exp id then begin
             Printtyp.exception_declaration id Format.std_formatter exn;
             if print_path then
               Format.print_string (Printf.sprintf " (* %s *)" path);
             Format.print_newline ()
           end
       | _ -> ())
    s

let warn_env_error e =
  Env.report_error Format.str_formatter e;
  let e = Format.flush_str_formatter () in
  Printf.eprintf "%s\n%!" e

let match_mod_expr expr mod_name = 
  let module E = Module_expression in
  match expr with
  | E.Exact name -> mod_name = name
  | E.Begins_with (_, rex)
  | E.Ends_with (_, rex)
  | E.Begins_and_ends (_, rex)
  | E.Contains (_, rex) -> Pcre.pmatch ~rex mod_name
  | E.Any -> true

let cmi_file = Pcre.regexp "\\.cmi$"
let modname_of_cmi f = 
  String.capitalize (Pcre.replace ~templ:"" ~rex:cmi_file f)

let cmi_of_modname n = (String.lowercase n) ^ ".cmi"

let cmi_files args mod_expr = 
  let module E = Module_expression in
  match mod_expr with
  | E.Exact mod_name ->
      let cmi_name = cmi_of_modname mod_name in
      List.fold_right
        (fun path acc -> 
           if Sys.file_exists (Filename.concat path cmi_name) then
             (mod_name, Filename.concat path cmi_name) :: acc
           else
             acc)
        args.path
        []
  | _ ->
      List.fold_right
        (fun path cmi_files ->
           Unix.fold_path
             ~f:(fun file cmi_files -> 
                   if Pcre.pmatch ~rex:cmi_file file then begin
                     let mod_name = modname_of_cmi file in
                     if match_mod_expr mod_expr mod_name then 
                       (mod_name, Filename.concat path file) :: cmi_files
                     else
                       cmi_files
                   end else
                     cmi_files)
             ~init:cmi_files
             path)
        args.path
        []

let rec matching_submods mod_expr s =
  match s with
  | Tsig_module (id, mt, _) :: tl when match_mod_expr mod_expr (Ident.name id) ->
      begin match mt with
      | Tmty_signature sg -> (Ident.name id, sg) :: matching_submods mod_expr tl
      | Tmty_functor (_, mt, _) ->
          begin match mt with
          | Tmty_signature sg -> (Ident.name id, sg) :: matching_submods mod_expr tl
          | _ -> matching_submods mod_expr tl
          end
      | Tmty_ident _ -> matching_submods mod_expr tl
      end
  | _ :: tl -> matching_submods mod_expr tl
  | [] -> []

let rec build_module_tree name root mod_expr = 
  match mod_expr with
  | [] -> Leaf (name, root)
  | mod_expr :: tl ->
      begin match matching_submods mod_expr root with
      | [] -> Leaf (name, root)
      | mods -> 
          let children = 
            List.map 
              (fun (name, sg) -> build_module_tree name sg tl)
              mods
          in
          Node (name, root, children)
      end

let rec extract_nodes depth path modtree =
  let concatpath path name = 
    if path = "" then name
    else (path ^ "." ^ name)
  in
  match modtree with
  | Leaf (name, sg) -> [(concatpath path name, depth, sg)]
  | Node (name, sg, children) ->
      (concatpath path name, depth, sg) ::
        (List.flatten
           (List.map
              (fun submod -> extract_nodes (depth + 1) (concatpath path name) submod)
              children))

let print_requested_stuff print_path name s args =
  match args.mode with
  | Find_type e -> print_type print_path name s e
  | Find_constructor e -> print_constructor print_path name s e
  | Find_polymorphic_variant e -> print_polymorphic_variant print_path name s e
  | Find_record_label e -> print_record_label print_path name s e
  | Find_value e -> print_value print_path name s e
  | Find_exception e -> print_exception print_path name s e
  | Find_class e -> print_class print_path name s e
  | Find_all e -> print_all print_path name s e
  | Find_module -> 
      Format.print_string name;
      Format.print_newline ()

let read_cmi_file filename =
  let ic = open_in_bin filename in
  try
    let buffer = String.create (String.length Config.cmi_magic_number) in
    really_input ic buffer 0 (String.length Config.cmi_magic_number);
    if buffer <> Config.cmi_magic_number then begin
      close_in ic;
      failwith (Printf.sprintf "not an interface: %s" filename)
    end;
    let (name, sg) = input_value ic in
    close_in ic;
    sg
  with exn ->
    close_in ic;
    failwith 
      (Printf.sprintf 
         "bad cmi file: %s, error: %s"
         filename
         (Printexc.to_string exn))

let module_exists args mod_exp = 
  let expr_len = List.length mod_exp in
  let mod_name = List.hd mod_exp in
  let submods = List.tl mod_exp in
  match cmi_files args mod_name with
  | [] -> false
  | cmi_files ->
      List.exists
        (fun (name, cmi_file) ->
           let s = read_cmi_file cmi_file in
           let sgs = 
             List.filter
               (fun (_, depth, _) -> depth = expr_len)
               (extract_nodes 1 "" (build_module_tree name s submods))
           in
           List.length sgs > 0)
        cmi_files

let gen_qualified args context = 
  let context = Array.of_list context in
  for i = 0 to Array.length context - 1 do
    try
      for j = i downto 0 do
        let maybe_parent = context.(j) in
        let child = context.(i) in
        let qualified = maybe_parent @ child in
        if module_exists args qualified then begin
          context.(i) <- qualified;
          raise Break
        end
      done
    with Break -> ()
  done;
  Array.to_list context

let () = 
  let args = parse_args () in
  let qualified_context = gen_qualified args args.context in
  let mod_exprs = 
    (* combine the list of fully qualified open modules with the
       module expressions that the user has specified following the
       compiler's rules about module opens for exact expressions, and
       combining everything for non exact expressions. *)
    match args.modname with
    | [] -> qualified_context
    | exps ->
        List.flatten
          (List.map
             (fun exp -> 
                if Module_expression.is_exact exp then
                  [try 
                     List.find_map
                       (fun qual -> 
                          let exp' = qual @ exp in
                          if module_exists args exp' then Some exp'
                          else None)
                       (List.rev qualified_context) (* look from the bottom up *)
                   with Not_found -> exp]
                else
                  exp ::
                    (List.rev_map
                       (fun qual -> qual @ exp)
                       qualified_context))
             exps)
  in
  List.iter
    (fun mod_expr ->
       try
         let expr_len = List.length mod_expr in
         let mod_name = List.hd mod_expr in
         let submods = List.tl mod_expr in
         let cmi_files = cmi_files args mod_name in
         List.iter
           (fun (name, cmi_file) ->
              let s = read_cmi_file cmi_file in
              let sgs = 
                List.filter
                  (fun (_, depth, _) -> depth = expr_len)
                  (extract_nodes 1 "" (build_module_tree name s submods))
              in
              let print_path = 
                List.length sgs > 1 || 
                  List.length cmi_files > 1 ||
                  List.length args.modname > 1
              in
              List.iter
                (fun (name, _,  sg) -> print_requested_stuff print_path name sg args)
                sgs)
           cmi_files
       with exn -> 
         Printf.eprintf
           "failed to operate on: \"%s\", %s\n%!"
           (String.concat " " (List.map Module_expression.to_string mod_expr))
           (Printexc.to_string exn))
    mod_exprs