(**************************************************************************) (* *) (* OCaml *) (* *) (* Leo White *) (* *) (* Copyright 1996 Institut National de Recherche en Informatique et *) (* en Automatique. *) (* *) (* All rights reserved. This file is distributed under the terms of *) (* the GNU Lesser General Public License version 2.1, with the *) (* special exception on linking described in the file LICENSE. *) (* *) (**************************************************************************) open Location (* Docstrings *) (* A docstring is "attached" if it has been inserted in the AST. This is used for generating unexpected docstring warnings. *) type ds_attached = | Unattached (* Not yet attached anything.*) | Info (* Attached to a field or constructor. *) | Docs (* Attached to an item or as floating text. *) (* A docstring is "associated" with an item if there are no blank lines between them. This is used for generating docstring ambiguity warnings. *) type ds_associated = | Zero (* Not associated with an item *) | One (* Associated with one item *) | Many (* Associated with multiple items (ambiguity) *) type docstring = { ds_body: string; ds_loc: Location.t; mutable ds_attached: ds_attached; mutable ds_associated: ds_associated; } (* List of docstrings *) let docstrings : docstring list ref = ref [] (* Warn for unused and ambiguous docstrings *) let warn_bad_docstrings () = if Warnings.is_active (Warnings.Unexpected_docstring true) then begin List.iter (fun ds -> match ds.ds_attached with | Info -> () | Unattached -> prerr_warning ds.ds_loc (Warnings.Unexpected_docstring true) | Docs -> match ds.ds_associated with | Zero | One -> () | Many -> prerr_warning ds.ds_loc (Warnings.Unexpected_docstring false)) (List.rev !docstrings) end (* Docstring constructors and destructors *) let docstring body loc = let ds = { ds_body = body; ds_loc = loc; ds_attached = Unattached; ds_associated = Zero; } in ds let register ds = docstrings := ds :: !docstrings let docstring_body ds = ds.ds_body let docstring_loc ds = ds.ds_loc (* Docstrings attached to items *) type docs = { docs_pre: docstring option; docs_post: docstring option; } let empty_docs = { docs_pre = None; docs_post = None } let doc_loc = {txt = "ocaml.doc"; loc = Location.none} let docs_attr ds = let open Parsetree in let body = ds.ds_body in let loc = ds.ds_loc in let exp = { pexp_desc = Pexp_constant (Pconst_string(body, loc, None)); pexp_loc = loc; pexp_loc_stack = []; pexp_attributes = []; } in let item = { pstr_desc = Pstr_eval (exp, []); pstr_loc = loc } in { attr_name = doc_loc; attr_payload = PStr [item]; attr_loc = loc } let add_docs_attrs docs attrs = let attrs = match docs.docs_pre with | None | Some { ds_body=""; _ } -> attrs | Some ds -> docs_attr ds :: attrs in let attrs = match docs.docs_post with | None | Some { ds_body=""; _ } -> attrs | Some ds -> attrs @ [docs_attr ds] in attrs (* Docstrings attached to constructors or fields *) type info = docstring option let empty_info = None let info_attr = docs_attr let add_info_attrs info attrs = match info with | None | Some {ds_body=""; _} -> attrs | Some ds -> attrs @ [info_attr ds] (* Docstrings not attached to a specific item *) type text = docstring list let empty_text = [] let empty_text_lazy = lazy [] let text_loc = {txt = "ocaml.text"; loc = Location.none} let text_attr ds = let open Parsetree in let body = ds.ds_body in let loc = ds.ds_loc in let exp = { pexp_desc = Pexp_constant (Pconst_string(body, loc, None)); pexp_loc = loc; pexp_loc_stack = []; pexp_attributes = []; } in let item = { pstr_desc = Pstr_eval (exp, []); pstr_loc = loc } in { attr_name = text_loc; attr_payload = PStr [item]; attr_loc = loc } let add_text_attrs dsl attrs = let fdsl = List.filter (function {ds_body=""} -> false| _ ->true) dsl in (List.map text_attr fdsl) @ attrs (* Find the first non-info docstring in a list, attach it and return it *) let get_docstring ~info dsl = let rec loop = function | [] -> None | {ds_attached = Info; _} :: rest -> loop rest | ds :: _ -> ds.ds_attached <- if info then Info else Docs; Some ds in loop dsl (* Find all the non-info docstrings in a list, attach them and return them *) let get_docstrings dsl = let rec loop acc = function | [] -> List.rev acc | {ds_attached = Info; _} :: rest -> loop acc rest | ds :: rest -> ds.ds_attached <- Docs; loop (ds :: acc) rest in loop [] dsl (* "Associate" all the docstrings in a list *) let associate_docstrings dsl = List.iter (fun ds -> match ds.ds_associated with | Zero -> ds.ds_associated <- One | (One | Many) -> ds.ds_associated <- Many) dsl (* Map from positions to pre docstrings *) let pre_table : (Lexing.position, docstring list) Hashtbl.t = Hashtbl.create 50 let set_pre_docstrings pos dsl = if dsl <> [] then Hashtbl.add pre_table pos dsl let get_pre_docs pos = try let dsl = Hashtbl.find pre_table pos in associate_docstrings dsl; get_docstring ~info:false dsl with Not_found -> None let mark_pre_docs pos = try let dsl = Hashtbl.find pre_table pos in associate_docstrings dsl with Not_found -> () (* Map from positions to post docstrings *) let post_table : (Lexing.position, docstring list) Hashtbl.t = Hashtbl.create 50 let set_post_docstrings pos dsl = if dsl <> [] then Hashtbl.add post_table pos dsl let get_post_docs pos = try let dsl = Hashtbl.find post_table pos in associate_docstrings dsl; get_docstring ~info:false dsl with Not_found -> None let mark_post_docs pos = try let dsl = Hashtbl.find post_table pos in associate_docstrings dsl with Not_found -> () let get_info pos = try let dsl = Hashtbl.find post_table pos in get_docstring ~info:true dsl with Not_found -> None (* Map from positions to floating docstrings *) let floating_table : (Lexing.position, docstring list) Hashtbl.t = Hashtbl.create 50 let set_floating_docstrings pos dsl = if dsl <> [] then Hashtbl.add floating_table pos dsl let get_text pos = try let dsl = Hashtbl.find floating_table pos in get_docstrings dsl with Not_found -> [] let get_post_text pos = try let dsl = Hashtbl.find post_table pos in get_docstrings dsl with Not_found -> [] (* Maps from positions to extra docstrings *) let pre_extra_table : (Lexing.position, docstring list) Hashtbl.t = Hashtbl.create 50 let set_pre_extra_docstrings pos dsl = if dsl <> [] then Hashtbl.add pre_extra_table pos dsl let get_pre_extra_text pos = try let dsl = Hashtbl.find pre_extra_table pos in get_docstrings dsl with Not_found -> [] let post_extra_table : (Lexing.position, docstring list) Hashtbl.t = Hashtbl.create 50 let set_post_extra_docstrings pos dsl = if dsl <> [] then Hashtbl.add post_extra_table pos dsl let get_post_extra_text pos = try let dsl = Hashtbl.find post_extra_table pos in get_docstrings dsl with Not_found -> [] (* Docstrings from parser actions *) module WithParsing = struct let symbol_docs () = { docs_pre = get_pre_docs (Parsing.symbol_start_pos ()); docs_post = get_post_docs (Parsing.symbol_end_pos ()); } let symbol_docs_lazy () = let p1 = Parsing.symbol_start_pos () in let p2 = Parsing.symbol_end_pos () in lazy { docs_pre = get_pre_docs p1; docs_post = get_post_docs p2; } let rhs_docs pos1 pos2 = { docs_pre = get_pre_docs (Parsing.rhs_start_pos pos1); docs_post = get_post_docs (Parsing.rhs_end_pos pos2); } let rhs_docs_lazy pos1 pos2 = let p1 = Parsing.rhs_start_pos pos1 in let p2 = Parsing.rhs_end_pos pos2 in lazy { docs_pre = get_pre_docs p1; docs_post = get_post_docs p2; } let mark_symbol_docs () = mark_pre_docs (Parsing.symbol_start_pos ()); mark_post_docs (Parsing.symbol_end_pos ()) let mark_rhs_docs pos1 pos2 = mark_pre_docs (Parsing.rhs_start_pos pos1); mark_post_docs (Parsing.rhs_end_pos pos2) let symbol_info () = get_info (Parsing.symbol_end_pos ()) let rhs_info pos = get_info (Parsing.rhs_end_pos pos) let symbol_text () = get_text (Parsing.symbol_start_pos ()) let symbol_text_lazy () = let pos = Parsing.symbol_start_pos () in lazy (get_text pos) let rhs_text pos = get_text (Parsing.rhs_start_pos pos) let rhs_post_text pos = get_post_text (Parsing.rhs_end_pos pos) let rhs_text_lazy pos = let pos = Parsing.rhs_start_pos pos in lazy (get_text pos) let symbol_pre_extra_text () = get_pre_extra_text (Parsing.symbol_start_pos ()) let symbol_post_extra_text () = get_post_extra_text (Parsing.symbol_end_pos ()) let rhs_pre_extra_text pos = get_pre_extra_text (Parsing.rhs_start_pos pos) let rhs_post_extra_text pos = get_post_extra_text (Parsing.rhs_end_pos pos) end include WithParsing module WithMenhir = struct let symbol_docs (startpos, endpos) = { docs_pre = get_pre_docs startpos; docs_post = get_post_docs endpos; } let symbol_docs_lazy (p1, p2) = lazy { docs_pre = get_pre_docs p1; docs_post = get_post_docs p2; } let rhs_docs pos1 pos2 = { docs_pre = get_pre_docs pos1; docs_post = get_post_docs pos2; } let rhs_docs_lazy p1 p2 = lazy { docs_pre = get_pre_docs p1; docs_post = get_post_docs p2; } let mark_symbol_docs (startpos, endpos) = mark_pre_docs startpos; mark_post_docs endpos; () let mark_rhs_docs pos1 pos2 = mark_pre_docs pos1; mark_post_docs pos2; () let symbol_info endpos = get_info endpos let rhs_info endpos = get_info endpos let symbol_text startpos = get_text startpos let symbol_text_lazy startpos = lazy (get_text startpos) let rhs_text pos = get_text pos let rhs_post_text pos = get_post_text pos let rhs_text_lazy pos = lazy (get_text pos) let symbol_pre_extra_text startpos = get_pre_extra_text startpos let symbol_post_extra_text endpos = get_post_extra_text endpos let rhs_pre_extra_text pos = get_pre_extra_text pos let rhs_post_extra_text pos = get_post_extra_text pos end (* (Re)Initialise all comment state *) let init () = docstrings := []; Hashtbl.reset pre_table; Hashtbl.reset post_table; Hashtbl.reset floating_table; Hashtbl.reset pre_extra_table; Hashtbl.reset post_extra_table