this repo has no description
at main 607 lines 24 kB view raw
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)))