1(**************************************************************************)
2(*                                                                        *)
3(*                                 OCaml                                  *)
4(*                                                                        *)
5(*             Maxence Guesdon, projet Cristal, INRIA Rocquencourt        *)
6(*                                                                        *)
7(*   Copyright 2001 Institut National de Recherche en Informatique et     *)
8(*     en Automatique.                                                    *)
9(*                                                                        *)
10(*   All rights reserved.  This file is distributed under the terms of    *)
11(*   the GNU Lesser General Public License version 2.1, with the          *)
12(*   special exception on linking described in the file LICENSE.          *)
13(*                                                                        *)
14(**************************************************************************)
15
16let no_blanks s =
17  let len = String.length s in
18  let buf = Buffer.create len in
19  for i = 0 to len - 1 do
20    match s.[i] with
21      ' ' | '\n' | '\t' | '\r' -> ()
22    | c -> Buffer.add_char buf c
23  done;
24  Buffer.contents buf
25
26let input_file_as_string nom =
27  let chanin = open_in_bin nom in
28  let len = 1024 in
29  let s = Bytes.create len in
30  let buf = Buffer.create len in
31  let rec iter () =
32    try
33      let n = input chanin s 0 len in
34      if n = 0 then
35        ()
36      else
37        (
38         Buffer.add_subbytes buf s 0 n;
39         iter ()
40        )
41    with
42      End_of_file -> ()
43  in
44  iter ();
45  close_in chanin;
46  Buffer.contents buf
47
48let split_string s chars =
49  let len = String.length s in
50  let rec iter acc pos =
51    if pos >= len then
52      match acc with
53        "" -> []
54      | _ -> [acc]
55    else
56      if List.mem s.[pos] chars then
57        match acc with
58          "" -> iter "" (pos + 1)
59        | _ -> acc :: (iter "" (pos + 1))
60      else
61        iter (Printf.sprintf "%s%c" acc s.[pos]) (pos + 1)
62  in
63  iter "" 0
64
65let split_with_blanks s = split_string s [' ' ; '\n' ; '\r' ; '\t' ]
66
67let list_concat sep =
68  let rec iter = function
69      [] -> []
70    | [h] -> [h]
71    | h :: q -> h :: sep :: iter q
72  in
73  iter
74
75let rec string_of_longident li =
76  match li with
77  | Longident.Lident s -> s
78  | Longident.Ldot(li, s) -> string_of_longident li ^ "." ^ s
79  | Longident.Lapply(l1, l2) ->
80      string_of_longident l1 ^ "(" ^ string_of_longident l2 ^ ")"
81
82let get_fields type_expr =
83  let (fields, _) = Ctype.flatten_fields (Ctype.object_fields type_expr) in
84  List.fold_left
85    (fun acc -> fun (label, field_kind, typ) ->
86      match field_kind with
87        Types.Fabsent ->
88          acc
89      | _ ->
90          if label = "*dummy method*" then
91            acc
92          else
93            acc @ [label, typ]
94    )
95    []
96    fields
97
98let rec string_of_text t =
99  let rec iter t_ele =
100    match t_ele with
101      | Odoc_types.Raw s
102      | Odoc_types.Code s
103      | Odoc_types.CodePre s
104      | Odoc_types.Verbatim s -> s
105      | Odoc_types.Bold t
106      | Odoc_types.Italic t
107      | Odoc_types.Center t
108      | Odoc_types.Left t
109      | Odoc_types.Right t
110      | Odoc_types.Emphasize t -> string_of_text t
111      | Odoc_types.List l ->
112          (String.concat ""
113             (List.map (fun t -> "\n- "^(string_of_text t)) l))^
114          "\n"
115      | Odoc_types.Enum l ->
116          let rec f n = function
117              [] -> "\n"
118            | t :: q ->
119                "\n"^(string_of_int n)^". "^(string_of_text t)^
120                (f (n + 1) q)
121          in
122          f 1 l
123      | Odoc_types.Newline -> "\n"
124      | Odoc_types.Block t -> "\t"^(string_of_text t)^"\n"
125      | Odoc_types.Title (_, _, t) -> "\n"^(string_of_text t)^"\n"
126      | Odoc_types.Latex s -> "{% "^s^" %}"
127      | Odoc_types.Link (s, t) ->
128          "["^s^"]"^(string_of_text t)
129      | Odoc_types.Ref (_name, _, Some text) ->
130          Printf.sprintf "[%s]" (string_of_text text)
131      | Odoc_types.Ref (name, _, None) ->
132          iter (Odoc_types.Code name)
133      | Odoc_types.Superscript t ->
134          "^{"^(string_of_text t)^"}"
135      | Odoc_types.Subscript t ->
136          "^{"^(string_of_text t)^"}"
137      | Odoc_types.Module_list l ->
138          string_of_text
139            (list_concat (Odoc_types.Raw ", ")
140               (List.map (fun s -> Odoc_types.Code s) l)
141            )
142      | Odoc_types.Index_list ->
143          ""
144      | Odoc_types.Custom (_, t) -> string_of_text t
145      | Odoc_types.Target _ -> ""
146  in
147  String.concat "" (List.map iter t)
148
149let string_of_author_list l =
150  match l with
151    [] ->
152      ""
153  | _ ->
154      "* "^Odoc_messages.authors^":\n"^
155      (String.concat ", " l)^
156      "\n"
157
158let string_of_version_opt v_opt =
159  match v_opt with
160    None -> ""
161  | Some v -> Odoc_messages.version^": "^v^"\n"
162
163let string_of_since_opt s_opt =
164  match s_opt with
165    None -> ""
166  | Some s -> Odoc_messages.since^" "^s^"\n"
167
168let string_of_raised_exceptions l =
169  match l with
170    [] -> ""
171  | (s, t) :: [] -> Odoc_messages.raises^" "^s^" "^(string_of_text t)^"\n"
172  | _ ->
173      Odoc_messages.raises^"\n"^
174      (String.concat ""
175         (List.map
176            (fun (ex, desc) -> "- "^ex^" "^(string_of_text desc)^"\n")
177            l
178         )
179      )^"\n"
180
181let string_of_see (see_ref, t) =
182  let t_ref =
183    match see_ref with
184      Odoc_types.See_url s -> [ Odoc_types.Link (s, t) ]
185    | Odoc_types.See_file s -> (Odoc_types.Code s) :: (Odoc_types.Raw " ") :: t
186    | Odoc_types.See_doc s -> (Odoc_types.Italic [Odoc_types.Raw s]) :: (Odoc_types.Raw " ") :: t
187  in
188  string_of_text t_ref
189
190let string_of_sees l =
191  match l with
192    [] -> ""
193  | see :: [] -> Odoc_messages.see_also^" "^(string_of_see see)^" \n"
194  | _ ->
195      Odoc_messages.see_also^"\n"^
196      (String.concat ""
197         (List.map
198            (fun see -> "- "^(string_of_see see)^"\n")
199            l
200         )
201      )^"\n"
202
203let string_of_return_opt return_opt =
204  match return_opt with
205    None -> ""
206  | Some s -> Odoc_messages.returns^" "^(string_of_text s)^"\n"
207
208let string_of_info i =
209  let module M = Odoc_types in
210  (match i.M.i_deprecated with
211    None -> ""
212  | Some d -> Odoc_messages.deprecated^"! "^(string_of_text d)^"\n")^
213  (match i.M.i_desc with
214    None -> ""
215  | Some d when d = [Odoc_types.Raw ""] -> ""
216  | Some d -> (string_of_text d)^"\n"
217  )^
218  (string_of_author_list i.M.i_authors)^
219  (string_of_version_opt i.M.i_version)^
220  (string_of_since_opt i.M.i_since)^
221  (string_of_raised_exceptions i.M.i_raised_exceptions)^
222  (string_of_return_opt i.M.i_return_value)
223
224let apply_opt f v_opt =
225  match v_opt with
226    None -> None
227  | Some v -> Some (f v)
228
229let string_of_date ?(absolute=false) ?(hour=true) d =
230  let add_0 s = if String.length s < 2 then "0"^s else s in
231  let t = (if absolute then Unix.gmtime else Unix.localtime) d in
232  (string_of_int (t.Unix.tm_year + 1900))^"-"^
233  (add_0 (string_of_int (t.Unix.tm_mon + 1)))^"-"^
234  (add_0 (string_of_int t.Unix.tm_mday))^
235  (
236   if hour then
237     " "^
238     (add_0 (string_of_int t.Unix.tm_hour))^":"^
239     (add_0 (string_of_int t.Unix.tm_min))
240   else
241     ""
242  )
243
244let current_date =
245  let time =
246    try
247      float_of_string (Sys.getenv "SOURCE_DATE_EPOCH")
248    with
249      Not_found -> Unix.time ()
250  in string_of_date ~absolute: true ~hour: false time
251
252
253let rec text_list_concat sep l =
254  match l with
255    [] -> []
256  | [t] -> t
257  | t :: q ->
258      t @ (sep :: (text_list_concat sep q))
259
260let rec text_no_title_no_list t =
261  let iter t_ele =
262    match t_ele with
263    | Odoc_types.Title (_,_,t) -> text_no_title_no_list t
264    | Odoc_types.List l
265    | Odoc_types.Enum l ->
266        (Odoc_types.Raw " ") ::
267        (text_list_concat
268           (Odoc_types.Raw ", ")
269           (List.map text_no_title_no_list l))
270    | Odoc_types.Raw _
271    | Odoc_types.Code _
272    | Odoc_types.CodePre _
273    | Odoc_types.Verbatim _
274    | Odoc_types.Ref _
275    | Odoc_types.Target _ -> [t_ele]
276    | Odoc_types.Newline ->  [Odoc_types.Newline]
277    | Odoc_types.Block t -> [Odoc_types.Block (text_no_title_no_list t)]
278    | Odoc_types.Bold t -> [Odoc_types.Bold (text_no_title_no_list t)]
279    | Odoc_types.Italic t -> [Odoc_types.Italic (text_no_title_no_list t)]
280    | Odoc_types.Center t -> [Odoc_types.Center (text_no_title_no_list t)]
281    | Odoc_types.Left t -> [Odoc_types.Left (text_no_title_no_list t)]
282    | Odoc_types.Right t -> [Odoc_types.Right (text_no_title_no_list t)]
283    | Odoc_types.Emphasize t -> [Odoc_types.Emphasize (text_no_title_no_list t)]
284    | Odoc_types.Latex s -> [Odoc_types.Latex s]
285    | Odoc_types.Link (s, t) -> [Odoc_types.Link (s, (text_no_title_no_list t))]
286    | Odoc_types.Superscript t -> [Odoc_types.Superscript (text_no_title_no_list t)]
287    | Odoc_types.Subscript t -> [Odoc_types.Subscript (text_no_title_no_list t)]
288    | Odoc_types.Module_list l ->
289        list_concat (Odoc_types.Raw ", ")
290          (List.map
291             (fun s -> Odoc_types.Ref (s, Some Odoc_types.RK_module, None))
292             l
293          )
294    | Odoc_types.Index_list -> []
295    | Odoc_types.Custom (s,t) -> [Odoc_types.Custom (s, text_no_title_no_list t)]
296  in
297  List.flatten (List.map iter t)
298
299let get_titles_in_text t =
300  let l = ref [] in
301  let rec iter_ele ele =
302    match ele with
303    | Odoc_types.Title (n,lopt,t) -> l := (n,lopt,t) :: !l
304    | Odoc_types.List l
305    | Odoc_types.Enum l -> List.iter iter_text l
306    | Odoc_types.Raw _
307    | Odoc_types.Code _
308    | Odoc_types.CodePre _
309    | Odoc_types.Verbatim _
310    | Odoc_types.Ref _ -> ()
311    | Odoc_types.Newline ->  ()
312    | Odoc_types.Block t
313    | Odoc_types.Bold t
314    | Odoc_types.Italic t
315    | Odoc_types.Center t
316    | Odoc_types.Left t
317    | Odoc_types.Right t
318    | Odoc_types.Emphasize t -> iter_text t
319    | Odoc_types.Latex _ -> ()
320    | Odoc_types.Link (_, t)
321    | Odoc_types.Superscript t
322    | Odoc_types.Subscript t  -> iter_text t
323    | Odoc_types.Module_list _ -> ()
324    | Odoc_types.Index_list -> ()
325    | Odoc_types.Custom (_, t) -> iter_text t
326    | Odoc_types.Target _ -> ()
327  and iter_text txt =
328    List.iter iter_ele txt
329  in
330  iter_text t;
331  List.rev !l
332
333let text_concat (sep : Odoc_types.text) l =
334  let rec iter = function
335      [] -> []
336    | [last] -> last
337    | h :: q -> h @ sep @ (iter q)
338  in
339  iter l
340
341(*********************************************************)
342let rec get_before_dot s =
343  try
344    let len = String.length s in
345    let n = String.index s '.' in
346    if n + 1 >= len then
347      (* The dot is the last character *)
348      (true, s, "")
349    else
350      match s.[n+1] with
351        ' ' | '\n' | '\r' | '\t' ->
352          (true, String.sub s 0 (n+1),
353           String.sub s (n+1) (len - n - 1))
354      | _ ->
355          let b, s2, s_after = get_before_dot (String.sub s (n + 1) (len - n - 1)) in
356          (b, (String.sub s 0 (n+1))^s2, s_after)
357  with
358    Not_found -> (false, s, "")
359
360let rec first_sentence_text t =
361  match t with
362    [] -> (false, [], [])
363  | ele :: q ->
364      let (stop, ele2, ele3_opt) = first_sentence_text_ele ele in
365      if stop then
366        (stop, [ele2],
367         match ele3_opt with None -> q | Some e -> e :: q)
368      else
369        let (stop2, q2, rest) = first_sentence_text q in
370        (stop2, ele2 :: q2, rest)
371
372
373and first_sentence_text_ele text_ele =
374  match text_ele with
375  | Odoc_types.Raw s ->
376      let b, s2, s_after = get_before_dot s in
377      (b, Odoc_types.Raw s2, Some (Odoc_types.Raw s_after))
378  | Odoc_types.Code _
379  | Odoc_types.CodePre _
380  | Odoc_types.Verbatim _ -> (false, text_ele, None)
381  | Odoc_types.Bold t ->
382      let (b, t2, t3) = first_sentence_text t in
383      (b, Odoc_types.Bold t2, Some (Odoc_types.Bold t3))
384  | Odoc_types.Italic t ->
385      let (b, t2, t3) = first_sentence_text t in
386      (b, Odoc_types.Italic t2, Some (Odoc_types.Italic t3))
387  | Odoc_types.Center t ->
388      let (b, t2, t3) = first_sentence_text t in
389      (b, Odoc_types.Center t2, Some (Odoc_types.Center t3))
390  | Odoc_types.Left t ->
391      let (b, t2, t3) = first_sentence_text t in
392      (b, Odoc_types.Left t2, Some (Odoc_types.Left t3))
393  | Odoc_types.Right t ->
394      let (b, t2, t3) = first_sentence_text t in
395      (b, Odoc_types.Right t2, Some (Odoc_types.Right t3))
396  | Odoc_types.Emphasize t ->
397      let (b, t2, t3) = first_sentence_text t in
398      (b, Odoc_types.Emphasize t2, Some (Odoc_types.Emphasize t3))
399  | Odoc_types.Block t ->
400      let (b, t2, t3) = first_sentence_text t in
401      (b, Odoc_types.Block t2, Some (Odoc_types.Block t3))
402  | Odoc_types.Title (n, l_opt, t) ->
403      let (b, t2, t3) = first_sentence_text t in
404      (b,
405       Odoc_types.Title (n, l_opt, t2),
406       Some (Odoc_types.Title (n, l_opt, t3)))
407  | Odoc_types.Newline ->
408      (true, Odoc_types.Raw "", Some Odoc_types.Newline)
409  | Odoc_types.List _
410  | Odoc_types.Enum _
411  | Odoc_types.Latex _
412  | Odoc_types.Link _
413  | Odoc_types.Ref _
414  | Odoc_types.Superscript _
415  | Odoc_types.Subscript _
416  | Odoc_types.Module_list _
417  | Odoc_types.Index_list -> (false, text_ele, None)
418  | Odoc_types.Custom _
419  | Odoc_types.Target _ -> (false, text_ele, None)
420
421
422let first_sentence_of_text t =
423  let (_,t2,_) = first_sentence_text t in
424  t2
425
426let first_sentence_and_rest_of_text t =
427  let (_,t1, t2) = first_sentence_text t in
428  (t1, t2)
429
430let remove_ending_newline s =
431  let len = String.length s in
432  if len <= 0 then
433    s
434  else
435    match s.[len-1] with
436      '\n' -> String.sub s 0 (len-1)
437    | _ -> s
438
439let search_string_backward ~pat =
440  let lenp = String.length pat in
441  let rec iter s =
442    let len = String.length s in
443    match compare len lenp with
444      -1 -> raise Not_found
445    | 0 -> if pat = s then 0 else raise Not_found
446    | _ ->
447        let pos = len - lenp in
448        let s2 = String.sub s pos lenp in
449        if s2 = pat then
450          pos
451        else
452          iter (String.sub s 0 pos)
453  in
454  fun ~s -> iter s
455
456
457
458(*********************************************************)
459
460let create_index_lists elements string_of_ele =
461  let rec f current acc0 acc1 acc2 = function
462      [] -> (acc0 :: acc1) @ [acc2]
463    | ele :: q ->
464        let s = string_of_ele ele in
465        match s with
466          "" -> f current acc0 acc1 (acc2 @ [ele]) q
467        | _ ->
468            let first = Char.uppercase_ascii s.[0] in
469            match first with
470              'A' .. 'Z' ->
471                if current = first then
472                  f current acc0 acc1 (acc2 @ [ele]) q
473                else
474                  f first acc0 (acc1 @ [acc2]) [ele] q
475            | _ ->
476                f current (acc0 @ [ele]) acc1 acc2 q
477  in
478  f '_' [] [] [] elements
479
480
481(*** for labels *)
482
483let is_optional = Btype.is_optional
484let label_name = Btype.label_name
485
486let remove_option typ =
487  let rec iter t =
488    match t with
489    | Types.Tconstr(path, [ty], _) when Path.same path Predef.path_option -> ty.Types.desc
490    | Types.Tconstr _
491    | Types.Tvar _
492    | Types.Tunivar _
493    | Types.Tpoly _
494    | Types.Tarrow _
495    | Types.Ttuple _
496    | Types.Tobject _
497    | Types.Tfield _
498    | Types.Tnil
499    | Types.Tvariant _
500    | Types.Tpackage _ -> t
501    | Types.Tlink t2
502    | Types.Tsubst t2 -> iter t2.Types.desc
503  in
504  { typ with Types.desc = iter typ.Types.desc }
505