File: interface_dumper.ml

package info (click to toggle)
ocaml-stdcompat 19-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 6,516 kB
  • sloc: ml: 27,806; sh: 875; makefile: 246
file content (228 lines) | stat: -rw-r--r-- 9,499 bytes parent folder | download | duplicates (2)
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
let run_interpreter ?(buffer_size = 4096)
    ~command_line ~module_name commander =
  let channels = Unix.open_process_full command_line (Unix.environment ()) in
  Fun.protect
    ~finally:(fun () ->
      assert (channels |> Unix.close_process_full = Unix.WEXITED 0))
    (fun () ->
      let in_channel, out_channel, err_channel = channels in
      let buffer = Buffer.create buffer_size in
      let rec wait_for_prompt () =
        let _ : int =
          Interface_tools.Buffer.add_channel_no_wait buffer in_channel
            buffer_size in
        if not (Interface_tools.Buffer.has_suffix buffer "# ") then
          wait_for_prompt () in
      wait_for_prompt ();
      let prolog = Buffer.contents buffer in
      let linefeed_index = String.index prolog '\n' in
      let version_line = String.sub prolog 0 linefeed_index in
      let version = Interface_tools.Version.of_string version_line in
      commander version (fun phrase ->
        let str =
          String.trim (Format.asprintf "%a@." Pprintast.top_phrase phrase) in
        let str =
          if Interface_tools.String.has_prefix ~prefix:";;" str then
            Interface_tools.String.suffix_from str 2
          else
            str in
        Printf.fprintf out_channel "\n%s\n\n" str;
        flush out_channel;
        Buffer.clear buffer;
        Interface_tools.Buffer.add_channel_to_the_end buffer in_channel
          ~continue:(fun () ->
            not (Interface_tools.Buffer.has_suffix buffer "#   "));
        if Buffer.length buffer > 4 then
          Buffer.truncate buffer (Buffer.length buffer - 4);
        Buffer.contents buffer))

let module_type_of_string ~module_name s =
  match
    let lexbuf = s |> Lexing.from_string in
    Lexing.set_filename lexbuf module_name;
    match Parse.interface lexbuf with
    | [{ psig_desc =
         Psig_module
           { pmd_type = module_type } }] ->
             module_type
    | [{ psig_desc =
         Psig_modtype
           { pmtd_type = Some module_type }}] ->
             module_type
    | _ -> failwith "Unexpected result"
  with
  | s ->
      s
  | exception ((Syntaxerr.Error _) as exn) ->
      prerr_endline s;
      (* Syntaxerr.report_error is deprecated *)
      begin match Location.error_of_exn exn with
      | Some (`Ok err) -> Location.print_report Format.err_formatter err
      | _ -> ()
      end;
      { pmty_desc = Pmty_signature []; pmty_loc = Location.none;
        pmty_attributes = [] }
  | exception e ->
      prerr_endline s;
      prerr_endline (Printexc.to_string e);
      { pmty_desc = Pmty_signature []; pmty_loc = Location.none;
        pmty_attributes = [] }

let refine_signature_item ~module_name
    (interpret : Parsetree.toplevel_phrase -> string)
    (signature_item : Parsetree.signature_item) =
  match signature_item.psig_desc with
  | Psig_value value_description ->
      let pstr_desc : Parsetree.structure_item_desc =
        Pstr_eval (Ast_helper.Exp.ident (Location.mkloc (Longident.Ldot (
          Lident module_name, value_description.pval_name.txt))
          !Ast_helper.default_loc), []) in
      let s = interpret (Ptop_def [{ pstr_desc; pstr_loc = Location.none }]) in
      let lines = String.split_on_char '\n' s in
      let rec chop_warning lines =
        match lines with
        | s :: s' :: _ when
            Interface_tools.String.has_prefix ~prefix:"Warning 3:" s
        || Interface_tools.String.has_prefix ~prefix:"Warning 3:" s ->
            Some s'
        | _ :: tl -> chop_warning tl
        | [] -> None in
      let warning = chop_warning lines in
      let signature_item =
        match warning with
        | None -> signature_item
        | Some warning ->
            let value_description = { value_description with
              pval_attributes = Ast_helper.Attr.mk
                (Location.mkloc "ocaml.deprecated" !Ast_helper.default_loc)
                (PStr [Ast_helper.Str.eval
                   (Ast_helper.Exp.constant (Ast_helper.Const.string warning))])
                :: value_description.pval_attributes } in
            { signature_item with psig_desc = Psig_value value_description } in
      signature_item
  | _ -> signature_item

let refine_module_type ~module_name interpret
    (module_type : Parsetree.module_type) =
  match module_type.pmty_desc with
  | Pmty_signature s ->
      let s = List.map (refine_signature_item ~module_name interpret) s in
      { module_type with pmty_desc = Pmty_signature s }
  | _ -> module_type

let self_name ~(module_name : Longident.t) (type_declaration : Parsetree.type_declaration)
    : Longident.t =
  if module_name = Lident "Pervasives" then
    Lident type_declaration.ptype_name.txt
  else
    Ldot (module_name, type_declaration.ptype_name.txt)

let rec remove_self_aliases_of_type_declaration ~module_name
    (type_declaration : Parsetree.type_declaration) =
  match type_declaration.ptype_manifest with
  | Some { ptyp_desc = Ptyp_constr ({ txt = ident }, _args) }
      when ident = self_name ~module_name type_declaration ->
        { type_declaration with ptype_manifest = None }
  | _ -> type_declaration

let rec remove_self_aliases_of_module_type ~module_name
    (module_type : Parsetree.module_type) =
  match module_type.pmty_desc with
  | Pmty_functor (Named (var, arg), body) ->
      let module_name : Longident.t =
        Lapply (module_name, Lident (Option.get var.txt)) in
      let body = remove_self_aliases_of_module_type ~module_name body in
      { module_type with pmty_desc = Pmty_functor (Named (var, arg), body) }
  | Pmty_signature s ->
      let s =
        s |> List.map @@ remove_self_aliases_of_signature_item ~module_name in
      { module_type with pmty_desc = Pmty_signature s }
  | _ -> module_type

and remove_self_aliases_of_signature_item ~module_name
    (item : Parsetree.signature_item) =
  match item.psig_desc with
  | Psig_type (rec_flag, list) ->
      let list = list |>
        List.map @@ remove_self_aliases_of_type_declaration ~module_name in
      { item with psig_desc = Psig_type (rec_flag, list) }
  | Psig_module module_declaration ->
      let module_name : Longident.t =
        Ldot (module_name, Option.get module_declaration.pmd_name.txt) in
      let pmd_type = remove_self_aliases_of_module_type ~module_name
          module_declaration.pmd_type in
      { item with psig_desc = Psig_module { module_declaration with pmd_type }}
  | _ -> item

let module_type_of_name ~command_line ~module_name =
  run_interpreter ~command_line ~module_name (fun version interpret ->
    let pstr_desc : Parsetree.structure_item_desc =
        let stdlib_module_name =
          if Interface_tools.Version.compare version
              { major = 4; minor = 7; patch = 0 } >= 0
              && module_name <> "Pervasives" && module_name <> "Stdlib" then
            if Interface_tools.Version.compare version
              { major = 4; minor = 13; patch = 0 } >= 0 then
              Printf.sprintf "Stdlib__%s" module_name
            else
              Printf.sprintf "Stdlib__%s" (String.uncapitalize_ascii module_name)
          else
            module_name in
      let module_expr : Parsetree.module_expr = {
        pmod_desc = Pmod_ident {
          txt = Lident stdlib_module_name; loc = Location.none };
        pmod_loc = Location.none;
        pmod_attributes = [] } in
      if Interface_tools.Version.compare version
          { major = 4; minor = 2; patch = 0 } >= 0 then
        Pstr_modtype {
          pmtd_name = { txt = module_name; loc = Location.none };
          pmtd_type = Some ({
            pmty_desc = Pmty_typeof module_expr;
            pmty_loc = Location.none;
            pmty_attributes = [] });
          pmtd_attributes = [Ast_helper.Attr.mk
            { Location.txt = "ocaml.warning"; loc = Location.none }
            (Parsetree.PStr [Ast_helper.Str.eval (
              Ast_helper.Exp.constant (Ast_helper.Const.string "-3"))])
      ];
          pmtd_loc = Location.none; }
      else
        Pstr_module {
          pmb_name = { txt = Some module_name; loc = Location.none };
          pmb_expr = module_expr;
          pmb_attributes = [];
          pmb_loc = Location.none; } in
    let s = interpret (Ptop_def [{ pstr_desc; pstr_loc = Location.none }]) in
    let module_type = module_type_of_string ~module_name s in
    let module_type =
      if Interface_tools.Version.compare version
          { major = 4; minor = 2; patch = 0 } >= 0 then
        module_type
      else
        remove_self_aliases_of_module_type ~module_name:(Lident module_name)
          module_type in
    let module_type = refine_module_type ~module_name interpret module_type in
    let _ : string = interpret (Ptop_def [Ast_helper.Str.eval (
          Ast_helper.Exp.apply
            (Ast_helper.Exp.ident
              (Location.mkloc (Longident.Lident "exit") !Ast_helper.default_loc))
            [Nolabel, Ast_helper.Exp.constant (Pconst_integer ("0", None))])]) in
    module_type)

let main () =
  let module_name, command_line =
    match Sys.argv with
    | [| _; module_name; command_line |] -> module_name, command_line
    | _ -> failwith "Bad usage" in
  let module_type = module_type_of_name ~command_line ~module_name in
  let signature =
    match module_type.pmty_desc with
    | Pmty_signature signature -> signature
    | _ -> failwith "Unexpected module type" in
  Format.fprintf Format.std_formatter "%a@."
    Pprintast.signature signature

let () =
  if not !Sys.interactive then
    main ()