this repo has no description
1type path = [ `Root of string | `Dot of path * string ]
2
3let expected_err :
4 (Format.formatter -> 'a -> unit) -> 'a -> Location_.span -> Error.t =
5 fun pp_a a -> Error.make "Expected %a." pp_a a
6
7let expected_err_str : string -> Location_.span -> Error.t =
8 expected_err Format.pp_print_string
9
10let unknown_reference_qualifier : string -> Location_.span -> Error.t =
11 Error.make "Unknown reference qualifier '%s'."
12
13let deprecated_reference_kind : string -> string -> Location_.span -> Error.t =
14 Error.make "'%s' is deprecated, use '%s' instead."
15
16let reference_kinds_do_not_match : string -> string -> Location_.span -> Error.t
17 =
18 Error.make "Old-style reference kind ('%s:') does not match new ('%s-')."
19
20let should_not_be_empty : what:string -> Location_.span -> Error.t =
21 fun ~what ->
22 Error.make "%s should not be empty." (Astring.String.Ascii.capitalize what)
23
24let not_allowed :
25 ?suggestion:string ->
26 what:string ->
27 in_what:string ->
28 Location_.span ->
29 Error.t =
30 fun ?suggestion ~what ~in_what ->
31 Error.make ?suggestion "%s is not allowed in %s."
32 (Astring.String.Ascii.capitalize what)
33 in_what
34
35(** Format a list in a human readable way: [A, B, or C]. *)
36let pp_hum_comma_separated pp_a ppf lst =
37 let rec loop hd = function
38 | [] -> Format.fprintf ppf "or %a" pp_a hd
39 | hd' :: tl' ->
40 Format.fprintf ppf "%a, " pp_a hd;
41 loop hd' tl'
42 in
43 match lst with [] -> () | [ a ] -> pp_a ppf a | hd :: tl -> loop hd tl
44
45let deprecated_reference_kind location kind replacement =
46 deprecated_reference_kind kind replacement location |> Error.raise_warning
47
48(* http://caml.inria.fr/pub/docs/manual-ocaml/ocamldoc.html#sec359. *)
49let match_ocamldoc_reference_kind (_location as loc) s :
50 [> Paths.Reference.tag_any ] option =
51 let d = deprecated_reference_kind in
52 match s with
53 | "module" -> Some `TModule
54 | "modtype" ->
55 d loc "modtype" "module-type";
56 Some `TModuleType
57 | "class" -> Some `TClass
58 | "classtype" ->
59 d loc "classtype" "class-type";
60 Some `TClassType
61 | "val" -> Some `TValue
62 | "type" -> Some `TType
63 | "exception" -> Some `TException
64 | "attribute" -> None
65 | "method" -> Some `TMethod
66 | "section" -> Some `TLabel
67 | "const" ->
68 d loc "const" "constructor";
69 Some `TConstructor
70 | "recfield" ->
71 d loc "recfield" "field";
72 Some `TField
73 | "childpage" -> Some `TChildPage
74 | "childmodule" -> Some `TChildModule
75 | _ -> None
76
77let match_extra_odoc_reference_kind (_location as loc) s :
78 [> Paths.Reference.tag_any ] option =
79 let d = deprecated_reference_kind in
80 match s with
81 | "class-type" -> Some `TClassType
82 | "constructor" -> Some `TConstructor
83 | "exn" ->
84 d loc "exn" "exception";
85 Some `TException
86 | "extension" -> Some `TExtension
87 | "extension-decl" -> Some `TExtensionDecl
88 | "field" -> Some `TField
89 | "instance-variable" -> Some `TInstanceVariable
90 | "label" ->
91 d loc "label" "section";
92 Some `TLabel
93 | "module-type" -> Some `TModuleType
94 | "page" -> Some `TPage
95 | "asset" -> Some `TAsset
96 | "value" ->
97 d loc "value" "val";
98 Some `TValue
99 | _ -> None
100
101type reference_kind = Paths.Reference.tag_any
102
103(* Ideally, [tokenize] would call this on every reference kind annotation during
104 tokenization, when generating the token list. However, that constrains the
105 phantom tag type to be the same for all tokens in the list (because lists are
106 homogeneous). So, the parser stores kinds as strings in the token list
107 instead, and this function is called on each string at the latest possible
108 time to prevent typing issues.
109
110 A secondary reason to delay parsing, and store strings in the token list, is
111 that we need the strings for user-friendly error reporting. *)
112let match_reference_kind location s : reference_kind =
113 match s with
114 | `None -> `TUnknown
115 | `Prefixed s | `Old_prefix s -> (
116 let result =
117 match match_ocamldoc_reference_kind location s with
118 | Some _ as kind -> kind
119 | None -> match_extra_odoc_reference_kind location s
120 in
121 match result with
122 | Some kind -> kind
123 | None -> unknown_reference_qualifier s location |> Error.raise_exception)
124
125type token = {
126 kind : [ `None | `Prefixed of string ];
127 identifier : string;
128 location : Location_.span;
129}
130
131type path_prefix = Path_prefix of string * Location_.span
132
133(* The string is scanned right-to-left, because we are interested in right-most
134 hyphens. The tokens are also returned in right-to-left order, because the
135 traversals that consume them prefer to look at the deepest identifier
136 first. *)
137let tokenize location s : token list * path_prefix option =
138 let rec scan_identifier started_at open_parenthesis_count index tokens =
139 match s.[index] with
140 | exception Invalid_argument _ ->
141 let identifier, location = identifier_ended started_at index in
142 ({ kind = `None; identifier; location } :: tokens, None)
143 | '-' when open_parenthesis_count = 0 ->
144 let identifier, location = identifier_ended started_at index in
145 scan_kind identifier location index (index - 1) tokens
146 | '.' when open_parenthesis_count = 0 ->
147 let identifier, location = identifier_ended started_at index in
148 scan_identifier index 0 (index - 1)
149 ({ kind = `None; identifier; location } :: tokens)
150 | '/' when open_parenthesis_count = 0 ->
151 let identifier, location = identifier_ended started_at index in
152 scan_path index ({ kind = `None; identifier; location } :: tokens)
153 | ')' ->
154 scan_identifier started_at
155 (open_parenthesis_count + 1)
156 (index - 1) tokens
157 | '(' when open_parenthesis_count > 0 ->
158 scan_identifier started_at
159 (open_parenthesis_count - 1)
160 (index - 1) tokens
161 | '"' -> (
162 try
163 scan_identifier started_at 0
164 (String.rindex_from s (index - 1) '"' - 1)
165 tokens
166 with _ ->
167 Error.raise_exception (Error.make "Unmatched quotation!" location))
168 | _ -> scan_identifier started_at open_parenthesis_count (index - 1) tokens
169 and identifier_ended started_at index =
170 let offset = index + 1 in
171 let length = started_at - offset in
172 let identifier = String.sub s offset length in
173 let identifier =
174 Astring.String.cuts ~sep:"\"" identifier
175 |> List.mapi (fun i s ->
176 if i mod 2 = 0 then
177 Astring.String.cuts s ~sep:" " |> String.concat ""
178 else s)
179 |> String.concat ""
180 in
181 let location = Location_.in_string s ~offset ~length location in
182
183 if identifier = "" then
184 should_not_be_empty ~what:"Identifier in reference" location
185 |> Error.raise_exception;
186
187 (identifier, location)
188 and scan_kind identifier identifier_location started_at index tokens =
189 match s.[index] with
190 | exception Invalid_argument _ ->
191 let kind, location = kind_ended identifier_location started_at index in
192 ({ kind; identifier; location } :: tokens, None)
193 | '.' ->
194 let kind, location = kind_ended identifier_location started_at index in
195 scan_identifier index 0 (index - 1)
196 ({ kind; identifier; location } :: tokens)
197 | '/' ->
198 let kind, location = kind_ended identifier_location started_at index in
199 scan_path index ({ kind; identifier; location } :: tokens)
200 | _ ->
201 scan_kind identifier identifier_location started_at (index - 1) tokens
202 and kind_ended identifier_location started_at index =
203 let offset = index + 1 in
204 let length = started_at - offset in
205 let kind = `Prefixed (String.sub s offset length) in
206 let location = Location_.in_string s ~offset ~length location in
207 let location = Location_.span [ location; identifier_location ] in
208 (kind, location)
209 and scan_path started_at tokens =
210 let location =
211 Location_.in_string s ~offset:0 ~length:(started_at + 1) location
212 in
213 (tokens, Some (Path_prefix (String.sub s 0 (started_at + 1), location)))
214 in
215
216 scan_identifier (String.length s) 0 (String.length s - 1) []
217 |> fun (toks, p) -> (List.rev toks, p)
218
219let expected ?(expect_paths = false) allowed location =
220 let unqualified = [ "an unqualified reference" ] in
221 let unqualified =
222 if expect_paths then "a path" :: unqualified else unqualified
223 in
224 let allowed = List.map (Printf.sprintf "'%s-'") allowed @ unqualified in
225 expected_err (pp_hum_comma_separated Format.pp_print_string) allowed location
226
227let parse_path whole_path_location p =
228 let segs = Astring.String.cuts ~sep:"/" p in
229 let check segs start =
230 let _finish =
231 List.fold_left
232 (fun offset seg ->
233 match seg with
234 | "" ->
235 let location =
236 Location_.in_string p ~offset ~length:0 whole_path_location
237 in
238 should_not_be_empty ~what:"Identifier in path reference" location
239 |> Error.raise_exception
240 | seg -> offset + String.length seg + 1)
241 start segs
242 in
243 ()
244 in
245 match segs with
246 | "." :: segs ->
247 check segs 2;
248 (`TRelativePath, segs)
249 | "" :: "" :: segs ->
250 check segs 2;
251 (`TCurrentPackage, segs)
252 | "" :: segs ->
253 check segs 1;
254 (`TAbsolutePath, segs)
255 | segs ->
256 check segs 0;
257 (`TRelativePath, segs)
258
259let parse_path_prefix (Path_prefix (p, path_location)) identifier
260 prefix_location =
261 parse_path (Location_.span [ path_location; prefix_location ]) (p ^ identifier)
262
263(* Parse references that do not contain a [/]. Raises errors and warnings. *)
264let parse whole_reference_location s :
265 Paths.Reference.t Error.with_errors_and_warnings =
266 let open Paths.Reference in
267 let open Names in
268 let parse_from_last_component { kind; identifier; location } old_kind tokens
269 path_prefix =
270 let rec signature { kind; identifier; location } tokens : Signature.t =
271 let kind = match_reference_kind location kind in
272 match tokens with
273 | [] -> (
274 match path_prefix with
275 | None -> (
276 match kind with
277 | (`TUnknown | `TModule | `TModuleType) as kind ->
278 `Root (identifier, kind)
279 | _ ->
280 expected ~expect_paths:true
281 [ "module"; "module-type" ]
282 location
283 |> Error.raise_exception)
284 | Some p -> (
285 match kind with
286 | `TUnknown | `TModule ->
287 `Module_path (parse_path_prefix p identifier location)
288 | _ ->
289 expected ~expect_paths:true [ "module" ] location
290 |> Error.raise_exception))
291 | next_token :: tokens -> (
292 match kind with
293 | `TUnknown ->
294 `Dot ((parent next_token tokens :> LabelParent.t), identifier)
295 | `TModule ->
296 `Module
297 (signature next_token tokens, ModuleName.make_std identifier)
298 | `TModuleType ->
299 `ModuleType
300 (signature next_token tokens, ModuleTypeName.make_std identifier)
301 | _ ->
302 expected ~expect_paths:true [ "module"; "module-type" ] location
303 |> Error.raise_exception)
304 and parent { kind; identifier; location } tokens : FragmentTypeParent.t =
305 let kind = match_reference_kind location kind in
306 match tokens with
307 | [] -> (
308 match path_prefix with
309 | None -> (
310 match kind with
311 | (`TUnknown | `TModule | `TModuleType | `TType) as kind ->
312 `Root (identifier, kind)
313 | _ ->
314 expected [ "module"; "module-type"; "type" ] location
315 |> Error.raise_exception)
316 | Some p -> (
317 match kind with
318 | `TUnknown | `TModule ->
319 `Module_path (parse_path_prefix p identifier location)
320 | _ ->
321 expected ~expect_paths:true [ "module" ] location
322 |> Error.raise_exception))
323 | next_token :: tokens -> (
324 match kind with
325 | `TUnknown ->
326 `Dot ((parent next_token tokens :> LabelParent.t), identifier)
327 | `TModule ->
328 `Module
329 (signature next_token tokens, ModuleName.make_std identifier)
330 | `TModuleType ->
331 `ModuleType
332 (signature next_token tokens, ModuleTypeName.make_std identifier)
333 | `TType ->
334 `Type (signature next_token tokens, TypeName.make_std identifier)
335 | _ ->
336 expected [ "module"; "module-type"; "type" ] location
337 |> Error.raise_exception)
338 in
339
340 let class_signature { kind; identifier; location } tokens : ClassSignature.t
341 =
342 let kind = match_reference_kind location kind in
343 match tokens with
344 | [] -> (
345 match kind with
346 | (`TUnknown | `TClass | `TClassType) as kind ->
347 `Root (identifier, kind)
348 | _ ->
349 expected [ "class"; "class-type" ] location
350 |> Error.raise_exception)
351 | next_token :: tokens -> (
352 match kind with
353 | `TUnknown ->
354 `Dot ((parent next_token tokens :> LabelParent.t), identifier)
355 | `TClass ->
356 `Class (signature next_token tokens, TypeName.make_std identifier)
357 | `TClassType ->
358 `ClassType
359 (signature next_token tokens, TypeName.make_std identifier)
360 | _ ->
361 expected [ "class"; "class-type" ] location
362 |> Error.raise_exception)
363 in
364
365 let label_parent_path kind path_prefix identifier location =
366 match kind with
367 | `TUnknown ->
368 `Any_path (parse_path_prefix path_prefix identifier location)
369 | `TModule ->
370 `Module_path (parse_path_prefix path_prefix identifier location)
371 | `TPage -> `Page_path (parse_path_prefix path_prefix identifier location)
372 | _ ->
373 expected ~expect_paths:true [ "module"; "page" ] location
374 |> Error.raise_exception
375 in
376
377 let any_path kind path_prefix identifier location =
378 match kind with
379 | `TUnknown ->
380 `Any_path (parse_path_prefix path_prefix identifier location)
381 | `TModule ->
382 `Module_path (parse_path_prefix path_prefix identifier location)
383 | `TPage -> `Page_path (parse_path_prefix path_prefix identifier location)
384 | `TAsset ->
385 `Asset_path (parse_path_prefix path_prefix identifier location)
386 | _ ->
387 expected ~expect_paths:true [ "module"; "page" ] location
388 |> Error.raise_exception
389 in
390
391 let rec label_parent { kind; identifier; location } tokens : LabelParent.t =
392 let kind = match_reference_kind location kind in
393 match tokens with
394 | [] -> (
395 match path_prefix with
396 | None -> (
397 match kind with
398 | ( `TUnknown | `TModule | `TModuleType | `TType | `TClass
399 | `TClassType | `TPage ) as kind ->
400 `Root (identifier, kind)
401 | _ ->
402 expected ~expect_paths:true
403 [
404 "module";
405 "module-type";
406 "type";
407 "class";
408 "class-type";
409 "page";
410 ]
411 location
412 |> Error.raise_exception)
413 | Some p -> label_parent_path kind p identifier location)
414 | next_token :: tokens -> (
415 match kind with
416 | `TUnknown -> `Dot (label_parent next_token tokens, identifier)
417 | `TModule ->
418 `Module
419 (signature next_token tokens, ModuleName.make_std identifier)
420 | `TModuleType ->
421 `ModuleType
422 (signature next_token tokens, ModuleTypeName.make_std identifier)
423 | `TType ->
424 `Type (signature next_token tokens, TypeName.make_std identifier)
425 | `TClass ->
426 `Class (signature next_token tokens, TypeName.make_std identifier)
427 | `TClassType ->
428 `ClassType
429 (signature next_token tokens, TypeName.make_std identifier)
430 | _ ->
431 expected ~expect_paths:true
432 [ "module"; "module-type"; "type"; "class"; "class-type" ]
433 location
434 |> Error.raise_exception)
435 in
436
437 let start_from_last_component { kind; identifier; location } old_kind tokens
438 =
439 let new_kind = match_reference_kind location kind in
440 let kind =
441 match old_kind with
442 | None -> new_kind
443 | Some (old_kind_string, old_kind_location) -> (
444 let old_kind =
445 match_reference_kind old_kind_location
446 (`Old_prefix old_kind_string)
447 in
448 match new_kind with
449 | `TUnknown -> old_kind
450 | _ ->
451 (if old_kind <> new_kind then
452 let new_kind_string =
453 match kind with `None -> "" | `Prefixed s -> s
454 in
455 reference_kinds_do_not_match old_kind_string new_kind_string
456 whole_reference_location
457 |> Error.raise_warning);
458 new_kind)
459 in
460
461 match tokens with
462 | [] -> (
463 match path_prefix with
464 | None -> `Root (identifier, kind)
465 | Some p -> any_path kind p identifier location)
466 | next_token :: tokens -> (
467 match kind with
468 | `TUnknown -> `Dot (label_parent next_token tokens, identifier)
469 | `TModule ->
470 `Module
471 (signature next_token tokens, ModuleName.make_std identifier)
472 | `TModuleType ->
473 `ModuleType
474 (signature next_token tokens, ModuleTypeName.make_std identifier)
475 | `TType ->
476 `Type (signature next_token tokens, TypeName.make_std identifier)
477 | `TConstructor ->
478 `Constructor
479 (parent next_token tokens, ConstructorName.make_std identifier)
480 | `TField ->
481 `Field (parent next_token tokens, FieldName.make_std identifier)
482 | `TUnboxedField ->
483 `UnboxedField (parent next_token tokens, UnboxedFieldName.make_std identifier)
484 | `TExtension ->
485 `Extension
486 (signature next_token tokens, ExtensionName.make_std identifier)
487 | `TExtensionDecl ->
488 `ExtensionDecl
489 (signature next_token tokens, ExtensionName.make_std identifier)
490 | `TException ->
491 `Exception
492 (signature next_token tokens, ExceptionName.make_std identifier)
493 | `TValue ->
494 `Value (signature next_token tokens, ValueName.make_std identifier)
495 | `TClass ->
496 `Class (signature next_token tokens, TypeName.make_std identifier)
497 | `TClassType ->
498 `ClassType
499 (signature next_token tokens, TypeName.make_std identifier)
500 | `TMethod ->
501 `Method
502 ( class_signature next_token tokens,
503 MethodName.make_std identifier )
504 | `TInstanceVariable ->
505 `InstanceVariable
506 ( class_signature next_token tokens,
507 InstanceVariableName.make_std identifier )
508 | `TLabel ->
509 `Label
510 (label_parent next_token tokens, LabelName.make_std identifier)
511 | `TChildPage | `TChildModule ->
512 let suggestion =
513 Printf.sprintf "'child-%s' should be first." identifier
514 in
515 not_allowed ~what:"Child label"
516 ~in_what:"the last component of a reference path" ~suggestion
517 location
518 |> Error.raise_exception
519 | `TPage ->
520 let suggestion =
521 Printf.sprintf "Reference pages as '<parent_path>/%s'."
522 identifier
523 in
524 not_allowed ~what:"Page label"
525 ~in_what:"on the right side of a dot" ~suggestion location
526 |> Error.raise_exception
527 | `TAsset ->
528 let suggestion =
529 Printf.sprintf "Reference assets as '<parent_path>/%s'."
530 identifier
531 in
532 not_allowed ~what:"Asset label"
533 ~in_what:"on the right side of a dot" ~suggestion location
534 |> Error.raise_exception)
535 in
536 start_from_last_component { kind; identifier; location } old_kind tokens
537 in
538 Error.catch_errors_and_warnings (fun () ->
539 let old_kind, s, location =
540 let rec find_old_reference_kind_separator index =
541 if index < 0 then raise Not_found
542 else
543 match s.[index] with
544 | ':' -> index
545 | ')' -> (
546 match String.rindex_from s index '(' with
547 | index -> find_old_reference_kind_separator (index - 1)
548 | exception (Not_found as exn) -> raise exn)
549 | _ -> find_old_reference_kind_separator (index - 1)
550 in
551 match find_old_reference_kind_separator (String.length s - 1) with
552 | index ->
553 let old_kind = String.trim (String.sub s 0 index) in
554 let old_kind_location =
555 Location_.set_end_as_offset_from_start index
556 whole_reference_location
557 in
558 let s = String.sub s (index + 1) (String.length s - (index + 1)) in
559 let location =
560 Location_.nudge_start (index + 1) whole_reference_location
561 in
562 (Some (old_kind, old_kind_location), s, location)
563 | exception Not_found -> (None, s, whole_reference_location)
564 in
565 match tokenize location s with
566 | last_token :: tokens, path_prefix ->
567 parse_from_last_component last_token old_kind tokens path_prefix
568 | [], _ ->
569 should_not_be_empty ~what:"Reference target" whole_reference_location
570 |> Error.raise_exception)
571
572(* Parse references that do not contain a [/]. Raises errors and warnings. *)
573let parse_asset whole_reference_location s :
574 Paths.Reference.Asset.t Error.with_errors_and_warnings =
575 let path = parse_path whole_reference_location s in
576 Error.catch_errors_and_warnings (fun () -> `Asset_path path)
577
578let read_path_longident location s =
579 let rec loop : string -> int -> path option =
580 fun s pos ->
581 try
582 let idx = String.rindex_from s pos '.' in
583 let name = String.sub s (idx + 1) (pos - idx) in
584 if String.length name = 0 then None
585 else
586 match loop s (idx - 1) with
587 | None -> None
588 | Some parent -> Some (`Dot (parent, name))
589 with Not_found ->
590 let name = String.sub s 0 (pos + 1) in
591 if String.length name = 0 then None else Some (`Root name)
592 in
593 Error.catch_warnings (fun () ->
594 match loop s (String.length s - 1) with
595 | Some r -> Ok (r :> path)
596 | None -> Error (expected_err_str "a valid path" location))
597
598let read_mod_longident location lid =
599 Error.catch_warnings (fun () ->
600 match Error.raise_warnings (parse location lid) with
601 | Error _ as e -> e
602 | Ok p -> (
603 match p with
604 | (`Root (_, (`TUnknown | `TModule)) | `Dot (_, _) | `Module (_, _))
605 as r ->
606 Ok r
607 | _ -> Error (expected_err_str "a reference to a module" location)))