diff --git a/CHANGES.md b/CHANGES.md index 6cb5bf928b..8f4b01775e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,11 @@ - Support for OxCaml modes (@art-w, #1454) - Fix OxCaml with-bounds for arbitrary types (@art-w, #1466) +### Performance +- Memoize doc-comment parsing and skip doc rebuilding during link when nothing needs resolution (@jonludlam, #1480) +- Store comment text as one node per run of words rather than one per word, + which makes `.odoc` and `.odocl` files substantially smaller (@jonludlam, #1487) + ### Fixed - Remove requirement for ppx_expect in tests (@jonludlam, #1445) - Fix resolving functor through `module type of` (@Leonidas-from-XIV, #1471) diff --git a/src/document/comment.ml b/src/document/comment.ml index 80382395bb..afec36fcad 100644 --- a/src/document/comment.ml +++ b/src/document/comment.ml @@ -148,9 +148,26 @@ module Reference = struct [ inline @@ Inline.Link link ]) end +(* Merged word runs keep the source whitespace, which isn't significant + inline, so collapse each run to a single space for rendering. *) +let collapse_whitespace s = + let b = Buffer.create (Stdlib.String.length s) in + let in_ws = ref false in + Stdlib.String.iter + (fun c -> + match c with + | ' ' | '\t' | '\n' | '\r' -> + if not !in_ws then Buffer.add_char b ' '; + in_ws := true + | c -> + Buffer.add_char b c; + in_ws := false) + s; + Buffer.contents b + let leaf_inline_element : Comment.leaf_inline_element -> Inline.one = function - | `Space -> inline @@ Text " " - | `Word s -> inline @@ Text s + | `Space s -> inline @@ Text (collapse_whitespace s) + | `Word s -> inline @@ Text (collapse_whitespace s) | `Code_span s -> inline @@ Source (source_of_code s) | `Math_span s -> inline @@ Math s | `Raw_markup (target, s) -> inline @@ Raw_markup (target, s) diff --git a/src/loader/doc_attr.ml b/src/loader/doc_attr.ml index ba39445f47..68b36fa2d2 100644 --- a/src/loader/doc_attr.ml +++ b/src/loader/doc_attr.ml @@ -150,14 +150,101 @@ let mk_alert_payload ~loc name p = let span = read_location loc in Location_.at span elt +(* The same doc-comment text is often attached to many definitions (OxCaml's + ppx_template can produce tens of thousands of copies of one comment), so + parses are memoized by raw text. The parsed AST bakes in absolute source + locations, so a cached parse is only reused when it is location- + insensitive: it produced no warnings and contains no headings, references + or [{!modules ...}], whose locations feed warnings and ambiguous-heading + detection during linking. Anything else is re-parsed per occurrence. *) + +let rec inline_element_needs_own_location + (x : Odoc_parser.Ast.inline_element) = + match x with + | `Reference _ -> true + | `Styled (_, xs) -> + List.exists + (fun e -> inline_element_needs_own_location (Odoc_parser.Loc.value e)) + xs + | `Space _ | `Word _ | `Code_span _ | `Raw_markup _ | `Link _ + | `Math_span _ -> + false + +let rec nestable_block_element_needs_own_location + (x : Odoc_parser.Ast.nestable_block_element) = + match x with + | `Paragraph xs -> + List.exists + (fun e -> inline_element_needs_own_location (Odoc_parser.Loc.value e)) + xs + | `Modules _ -> true + | `Media (_, href, _, _) -> ( + match Odoc_parser.Loc.value href with + | `Reference _ -> true + | `Link _ -> false) + | `List (_, _, yss) -> + List.exists + (List.exists (fun e -> + nestable_block_element_needs_own_location (Odoc_parser.Loc.value e))) + yss + | `Table ((grid, _), _) -> + List.exists + (List.exists (fun (cell, _) -> + List.exists + (fun e -> + nestable_block_element_needs_own_location + (Odoc_parser.Loc.value e)) + cell)) + grid + | `Code_block _ | `Verbatim _ | `Math_block _ -> false + +let tag_needs_own_location (x : Odoc_parser.Ast.tag) = + match x with + | `Deprecated c | `Param (_, c) | `Raise (_, c) | `Return c + | `See (_, _, c) | `Before (_, c) | `Children_order c | `Toc_status c + | `Order_category c | `Short_title c -> + List.exists + (fun e -> + nestable_block_element_needs_own_location (Odoc_parser.Loc.value e)) + c + | `Author _ | `Since _ | `Version _ | `Canonical _ | `Inline | `Open + | `Closed | `Hidden -> + false + +let block_element_needs_own_location (x : Odoc_parser.Ast.block_element) = + match x with + | `Heading _ -> true + | `Tag t -> tag_needs_own_location t + | #Odoc_parser.Ast.nestable_block_element as x -> + nestable_block_element_needs_own_location x + +let ast_needs_own_location (ast : Odoc_parser.Ast.t) = + List.exists + (fun e -> block_element_needs_own_location (Odoc_parser.Loc.value e)) + ast + +let doc_cache : (string, Odoc_parser.Ast.t) Hashtbl.t = Hashtbl.create 256 + let attached ~warnings_tag internal_tags parent attrs = let rec loop acc_docs acc_alerts = function | attr :: rest -> ( match parse_attribute attr with | Some (`Doc (str, loc)) -> let ast_docs = - Odoc_parser.parse_comment ~location:(pad_loc loc) ~text:str - |> Error.raise_parser_warnings + match Hashtbl.find_opt doc_cache str with + | Some cached -> cached + | None -> + let parsed = + Odoc_parser.parse_comment ~location:(pad_loc loc) ~text:str + in + let ast = + Semantics.merge_ast (Error.raise_parser_warnings parsed) + in + (match Odoc_parser.warnings parsed with + | [] when not (ast_needs_own_location ast) -> + Hashtbl.replace doc_cache str ast + | _ -> ()); + ast in loop (List.rev_append ast_docs acc_docs) acc_alerts rest | Some (`Alert (name, p, loc)) -> diff --git a/src/model/comment.ml b/src/model/comment.ml index 478e47e432..841319f5c4 100644 --- a/src/model/comment.ml +++ b/src/model/comment.ml @@ -15,7 +15,7 @@ type media = [ `Image | `Audio | `Video ] type raw_markup_target = string type leaf_inline_element = - [ `Space + [ `Space of string | `Word of string | `Code_span of string | `Math_span of string @@ -155,7 +155,7 @@ let to_string (l : link_content) = | `Code_span s -> s | `Word w -> w | `Math_span m -> m - | `Space -> " " + | `Space s -> s | `Styled (_, is) -> s_of_is is | `Raw_markup (_, r) -> r and s_of_is is = diff --git a/src/model/frontmatter.ml b/src/model/frontmatter.ml index fd6aa5d3b9..c3481a617c 100644 --- a/src/model/frontmatter.ml +++ b/src/model/frontmatter.ml @@ -77,7 +77,7 @@ let parse_children_order loc (co : tag_payload) = | [] -> Ok (Location_.at loc (Children_order (List.rev acc))) | ({ Location_.value = `Word word; _ } as w) :: tl -> parse_words ({ w with value = parse_child word } :: acc) tl - | { Location_.value = `Space; _ } :: tl -> parse_words acc tl + | { Location_.value = `Space _; _ } :: tl -> parse_words acc tl | { location; _ } :: _ -> Error (Error.make "Only words are accepted when specifying children order" diff --git a/src/model/semantics.ml b/src/model/semantics.ml index 279d61604d..4c85cdf9fc 100644 --- a/src/model/semantics.ml +++ b/src/model/semantics.ml @@ -128,7 +128,7 @@ let leaf_inline_element : fun element -> match element with | { value = `Word _ | `Code_span _ | `Math_span _; _ } as element -> element - | { value = `Space _; _ } -> Location.same element `Space + | { value = `Space s; _ } -> Location.same element (`Space s) | { value = `Raw_markup (target, s); location } -> ( match target with | Some invalid_target @@ -145,6 +145,97 @@ let leaf_inline_element : Location.same element (`Code_span s) | Some target -> Location.same element (`Raw_markup (target, s))) +(* Rebuild [elt] only if the merge changed something *) +let same elt content content' mk = + if content' == content then elt + else { elt with Location_.value = mk content' } + +(* Merge runs of [`Word]/[`Space] into one [`Word] *) +let rec merge_ast_inline_elements elements = + let flush run acc = + match run with + | [] -> acc + | [ single ] -> single :: acc + | _ -> + let text_of { Location_.value; _ } = + match value with `Word w -> w | `Space s -> s | _ -> assert false + in + let value = `Word (String.concat ~sep:"" (List.rev_map text_of run)) in + let location = + Location.span (List.rev_map (fun e -> e.Location_.location) run) + in + { Location_.value; location } :: acc + in + let rec loop run acc = function + | [] -> List.rev (flush run acc) + | ({ Location_.value = `Word _ | `Space _; _ } as elt) :: tl -> + loop (elt :: run) acc tl + | elt :: tl -> loop [] (merge_ast_inline_element elt :: flush run acc) tl + in + let merged = loop [] [] elements in + let unchanged = + try List.for_all2 ( == ) merged elements with Invalid_argument _ -> false + in + if unchanged then elements else merged + +and merge_ast_inline_element elt = + let same content mk = + same elt content (merge_ast_inline_elements content) mk + in + match elt.Location_.value with + | `Styled (style, content) -> same content (fun c -> `Styled (style, c)) + | `Reference (kind, target, content) -> + same content (fun c -> `Reference (kind, target, c)) + | `Link (target, content) -> same content (fun c -> `Link (target, c)) + | _ -> elt + +let rec merge_ast_nestable elt = + match elt.Location_.value with + | `Paragraph content -> + same elt content (merge_ast_inline_elements content) (fun c -> + `Paragraph c) + | `List (kind, weight, items) -> + let items = List.map (List.map merge_ast_nestable) items in + { elt with Location_.value = `List (kind, weight, items) } + | `Table ((grid, align), weight) -> + let grid = + List.map + (List.map (fun (cell, kind) -> + (List.map merge_ast_nestable cell, kind))) + grid + in + { elt with Location_.value = `Table ((grid, align), weight) } + | `Code_block _ | `Verbatim _ | `Modules _ | `Math_block _ | `Media _ -> elt + +(* Internal tags are skipped: they parse their payload word by word. *) +let merge_ast (ast : Ast.t) : Ast.t = + let nestables = List.map merge_ast_nestable in + let tag : Ast.tag -> Ast.tag = function + | `Deprecated content -> `Deprecated (nestables content) + | `Param (s, content) -> `Param (s, nestables content) + | `Raise (s, content) -> `Raise (s, nestables content) + | `Return content -> `Return (nestables content) + | `See (kind, s, content) -> `See (kind, s, nestables content) + | `Before (s, content) -> `Before (s, nestables content) + | (`Author _ | `Since _ | `Version _ | #Ast.internal_tag) as t -> t + in + List.map + (fun elt -> + match elt.Location_.value with + | `Heading (level, label, content) -> + same elt content (merge_ast_inline_elements content) (fun c -> + `Heading (level, label, c)) + | `Tag t -> { elt with Location_.value = `Tag (tag t) } + | #Ast.nestable_block_element as v -> + let nested = merge_ast_nestable { elt with Location_.value = v } in + if nested.Location_.value == v then elt + else + { + elt with + Location_.value = (nested.Location_.value :> Ast.block_element); + }) + ast + type surrounding = [ `Link of string * Odoc_parser.Ast.inline_element Location_.with_location list @@ -352,6 +443,23 @@ let generate_heading_label : Comment.inline_element with_location list -> string Bytes.set result index c); Bytes.unsafe_to_string result in + (* Whitespace runs inside a merged [`Word] stood for a single [`Space], so + become a single hyphen. Code spans keep the per-character version. *) + let hyphenate_word_runs s = + let b = Buffer.create (String.length s) in + let in_ws = ref false in + Stdlib.String.iter + (fun c -> + match c with + | ' ' | '\t' | '\r' | '\n' -> + if not !in_ws then Buffer.add_char b '-'; + in_ws := true + | c -> + Buffer.add_char b (Astring.Char.Ascii.lowercase c); + in_ws := false) + s; + Buffer.contents b + in let strip_locs li = List.map (fun ele -> ele.Location.value) li in (* Perhaps this should be done using a [Buffer.t]; we can switch to that as @@ -361,8 +469,8 @@ let generate_heading_label : Comment.inline_element with_location list -> string | element :: more -> let anchor = match (element : Comment.inline_element) with - | `Space -> anchor ^ "-" - | `Word w -> anchor ^ Astring.String.Ascii.lowercase w + | `Space _ -> anchor ^ "-" + | `Word w -> anchor ^ hyphenate_word_runs w | `Code_span c | `Math_span c -> anchor ^ replace_spaces_with_hyphens_and_lowercase c | `Raw_markup _ -> @@ -576,7 +684,7 @@ let handle_internal_tags (type a) tags : a handle_internal_tags -> a = function in let lines = let do_ parse loc els = - let els = nestable_block_elements els in + let els = List.map nestable_block_element els in match parse loc els with | Ok res -> Some res | Error e -> @@ -603,7 +711,7 @@ let ast_to_comment ~internal_tags ~tags_allowed ~parent_of_sections (ast : Ast.t) alerts = Error.catch_warnings (fun () -> let status = { tags_allowed; parent_of_sections } in - let ast, tags = strip_internal_tags ast in + let ast, tags = strip_internal_tags (merge_ast ast) in let elts = top_level_block_elements status ast |> append_alerts_to_comment alerts in diff --git a/src/model/semantics.mli b/src/model/semantics.mli index 09d750da92..f47c4bd87e 100644 --- a/src/model/semantics.mli +++ b/src/model/semantics.mli @@ -11,6 +11,11 @@ type sections_allowed = [ `All | `No_titles | `None ] type alerts = [ `Tag of [ `Alert of string * string option ] ] Location_.with_location list +val merge_ast : Odoc_parser.Ast.t -> Odoc_parser.Ast.t +(** Merge runs of [`Word]/[`Space] into single [`Word]s. Idempotent; the loader + applies it before its parse cache so that repeated comments share the merged + nodes. *) + val ast_to_comment : internal_tags:'tags handle_internal_tags -> tags_allowed:bool -> diff --git a/src/model_desc/comment_desc.ml b/src/model_desc/comment_desc.ml index d72e20fc9e..ff21298f0f 100644 --- a/src/model_desc/comment_desc.ml +++ b/src/model_desc/comment_desc.ml @@ -6,7 +6,7 @@ open Paths_desc let ignore_loc x = x.Location_.value type general_inline_element = - [ `Space + [ `Space of string | `Word of string | `Code_span of string | `Math_span of string @@ -72,7 +72,7 @@ let rec inline_element : general_inline_element t = in Variant (function - | `Space -> C0 "`Space" + | `Space s -> C ("`Space", s, string) | `Word x -> C ("`Word", x, string) | `Code_span x -> C ("`Code_span", x, string) | `Math_span x -> C ("`Math_span", x, string) diff --git a/src/odoc/odoc_link.ml b/src/odoc/odoc_link.ml index f51f1fa144..d04049f2ec 100644 --- a/src/odoc/odoc_link.ml +++ b/src/odoc/odoc_link.ml @@ -27,11 +27,11 @@ let content_for_hidden_modules = let sentence = [ `Word "This"; - `Space; + `Space " "; `Word "module"; - `Space; + `Space " "; `Word "is"; - `Space; + `Space " "; `Word "hidden."; ] in diff --git a/src/search/text.ml b/src/search/text.ml index da112c39e7..5a1c5991e2 100644 --- a/src/search/text.ml +++ b/src/search/text.ml @@ -72,7 +72,7 @@ module Of_comments = struct | `Code_span s -> s | `Word w -> w | `Math_span m -> m - | `Space -> " " + | `Space s -> s | `Reference (_, c) -> link_content c | `Link (_, c) -> link_content c | `Styled (_, b) -> inlines b diff --git a/src/xref2/link.ml b/src/xref2/link.ml index 2620a49065..7cda304519 100644 --- a/src/xref2/link.ml +++ b/src/xref2/link.ml @@ -232,6 +232,78 @@ and module_path : Env.t -> Paths.Path.Module.t -> Paths.Path.Module.t = Errors.report ~what:(`Module_path cp) ~tools_error:e `Resolve; p) +(* Comment.docs produced by Doc_attr can be huge in ppx_template-heavy code + (Container_intf in OxCaml's base has ~155K doc-comment instances), and the vast + majority of them are plain prose that has nothing for this module to do: + no references to resolve, no headings to check for ambiguity, no + {!modules ...} lists to resolve. Rebuilding such a doc word-by-word via + the List.map below is pure overhead, so we short-circuit and return the + input unchanged whenever it contains none of the constructs that this + pass actually touches. *) +let rec comment_inline_element_needs_resolving (x : Comment.inline_element) = + match x with + | `Reference _ -> true + | `Styled (_, xs) -> + List.exists + (fun e -> comment_inline_element_needs_resolving e.Location_.value) + xs + | `Space _ | `Word _ | `Code_span _ | `Math_span _ | `Raw_markup _ | `Link _ + -> + false + +let rec comment_nestable_block_element_needs_resolving + (x : Comment.nestable_block_element) = + match x with + | `Paragraph elts -> + List.exists + (fun e -> comment_inline_element_needs_resolving e.Location_.value) + elts + | `List (_, yss) -> + List.exists + (List.exists (fun e -> + comment_nestable_block_element_needs_resolving e.Location_.value)) + yss + | `Table { data; _ } -> + List.exists + (List.exists (fun (cell, _) -> + List.exists + (fun e -> + comment_nestable_block_element_needs_resolving + e.Location_.value) + cell)) + data + | `Modules _ -> true + | `Media (`Reference _, _, _) -> true + | `Media (`Link _, _, _) -> false + | `Code_block _ | `Verbatim _ | `Math_block _ -> false + +let comment_tag_needs_resolving (x : Comment.tag) = + match x with + | `Raise (`Reference _, _) -> true + | `Raise (`Code_span _, c) + | `Deprecated c + | `Param (_, c) + | `Return c + | `See (_, _, c) + | `Before (_, c) -> + List.exists + (fun e -> + comment_nestable_block_element_needs_resolving e.Location_.value) + c + | `Author _ | `Since _ | `Version _ | `Alert _ -> false + +let comment_block_element_needs_resolving (x : Comment.block_element) = + match x with + | #Comment.nestable_block_element as x -> + comment_nestable_block_element_needs_resolving x + | `Heading _ -> true + | `Tag t -> comment_tag_needs_resolving t + +let doc_needs_resolving (d : Comment.docs) = + List.exists + (fun e -> comment_block_element_needs_resolving e.Location_.value) + d.Comment.elements + let rec comment_inline_element : loc:_ -> Env.t -> @@ -412,16 +484,18 @@ and with_location : type a. { value; location = loc } and comment_docs env parent d = - { - Comment.elements = - List.rev_map - (with_location - (comment_block_element env d.Comment.warnings_tag - (parent :> Id.LabelParent.t))) - d.Comment.elements - |> List.rev; - warnings_tag = d.warnings_tag; - } + if not (doc_needs_resolving d) then d + else + { + Comment.elements = + List.rev_map + (with_location + (comment_block_element env d.Comment.warnings_tag + (parent :> Id.LabelParent.t))) + d.Comment.elements + |> List.rev; + warnings_tag = d.warnings_tag; + } and comment env parent = function | `Stop -> `Stop diff --git a/test/frontmatter/frontmatter.t/run.t b/test/frontmatter/frontmatter.t/run.t index d3a9cab135..9d1e02b64e 100644 --- a/test/frontmatter/frontmatter.t/run.t +++ b/test/frontmatter/frontmatter.t/run.t @@ -57,11 +57,7 @@ When there is one frontmatter, it is extracted from the content: }, [ { - "`Word": "One" - }, - "`Space", - { - "`Word": "frontmatter" + "`Word": "One frontmatter" } ] ] @@ -112,11 +108,7 @@ When there is more than one children order, we raise a warning and keep only the }, [ { - "`Word": "Two" - }, - "`Space", - { - "`Word": "frontmatters" + "`Word": "Two frontmatters" } ] ] diff --git a/test/frontmatter/short_title.t/run.t b/test/frontmatter/short_title.t/run.t index 67f3a0e3b6..8fdc47a0f4 100644 --- a/test/frontmatter/short_title.t/run.t +++ b/test/frontmatter/short_title.t/run.t @@ -6,7 +6,7 @@ Normal use > EOF $ odoc compile --parent-id pkg --output-dir _odoc index.mld $ odoc_print _odoc/pkg/page-index.odoc | jq .frontmatter.short_title -c - {"Some":[{"`Word":"First"},"`Space",{"`Word":"try"}]} + {"Some":[{"`Word":"First"},{"`Space":" "},{"`Word":"try"}]} With inline content @@ -16,7 +16,7 @@ With inline content > EOF $ odoc compile --parent-id pkg --output-dir _odoc index.mld $ odoc_print _odoc/pkg/page-index.odoc | jq .frontmatter.short_title -c - {"Some":[{"`Word":"with"},"`Space",{"`Code_span":"code"},"`Space",{"`Word":"and"},"`Space",{"`Styled":["`Emphasis",[{"`Word":"emphasized"}]]},"`Space",{"`Word":"content"}]} + {"Some":[{"`Word":"with"},{"`Space":" "},{"`Code_span":"code"},{"`Space":" "},{"`Word":"and"},{"`Space":" "},{"`Styled":["`Emphasis",[{"`Word":"emphasized"}]]},{"`Space":" "},{"`Word":"content"}]} With reference or link @@ -26,7 +26,7 @@ With reference or link > EOF $ odoc compile --parent-id pkg --output-dir _odoc index.mld $ odoc_print _odoc/pkg/page-index.odoc | jq .frontmatter.short_title -c - {"Some":[{"`Word":"with"},"`Space","`Space",{"`Word":"and"},"`Space"]} + {"Some":[{"`Word":"with"},{"`Space":" "},{"`Space":" "},{"`Word":"and"},{"`Space":" "}]} With other block diff --git a/test/generators/html/Alias-X.html b/test/generators/html/Alias-X.html index 5f50308e96..2ca26c3fe8 100644 --- a/test/generators/html/Alias-X.html +++ b/test/generators/html/Alias-X.html @@ -24,8 +24,9 @@
Alias.XModule Foo__X documentation. This should appear in the documentation - for the alias to this module 'X' +
+ Module Foo__X documentation. This should appear in the documentation + for the alias to this module 'X'
BugsRenders as
- val repeat : 'a -> 'b -> 'c * 'd * 'e * 'f before
- https://github.com/ocaml/odoc/pull/1173
+ val repeat : 'a -> 'b -> 'c * 'd * 'e * 'f
+ before https://github.com/ocaml/odoc/pull/1173
Bugs_post_406Let-open in class types, https://github.com/ocaml/odoc/issues/543 - This was added to the language in 4.06 +
+ Let-open in class types, https://github.com/ocaml/odoc/issues/543 This + was added to the language in 4.06
Include2.Y_include_docDoc attached to include Y. Y's top-comment
- shouldn't appear here.
+
Doc attached to include Y. Y
+ 's top-comment shouldn't appear here.
Include2The include Y below should have the synopsis from
- Y's top-comment attached to it.
+
The include Y below should have the synopsis from
+ Y's top-comment attached to it.
Some text.
-And let's include it again, but without inlining it this time: - the ToC shouldn't grow. +
+ And let's include it again, but without inlining it this time: the ToC + shouldn't grow.
MarkupLet's get these done first, because sections will be used to break - up the rest of this test. +
+ Let's get these done first, because sections will be used to break up the + rest of this test.
Besides the section heading above, there are also
and
but odoc has banned deeper headings. There are also title headings, - but they are only allowed in mld files. +
+ but odoc has banned deeper headings. There are also title headings, but + they are only allowed in mld files.
Sections can have attached - Anchors, and it is possible - to link to them. Links to - section headers should not be set in source code style. + Anchors, and it is possible to + link + to them. Links to section headers should not be set in source code + style.
Individual paragraphs can have a heading.
Parts of a longer paragraph that can be considered alone can also - have headings. +
+ Parts of a longer paragraph that can be considered alone can also have + headings.
This paragraph has some styled elements: bold and italic , bold italic, emphasis, emphasis within emphasis, bold italic, superscript, subscript - . The line spacing should be enough for superscripts and subscripts - not to look odd. + . The line spacing should be enough for superscripts and subscripts not + to look odd.
Note:
In italics emphasis is rendered as normal text while
- emphasis in emphasis is rendered in
- italics.
+ emphasis in emphasis
+ is rendered in italics.
It also work the same in
links in italics with
@@ -114,55 +118,57 @@
code is a different kind of markup that doesn't allow
- nested markup.
+
code
+ is a different kind of markup that doesn't allow nested markup.
It's possible for two markup elements to appear next to - each other and have a space, and appear nextto each - other with no space. It doesn't matter how much space - it was in the source: in this sentence, it was two space characters. - And in this one, there is a newline. + each other and have a space, and appear nextto + each other with no space. It doesn't matter how much + space it was in the source: in this sentence, it was two space + characters. And in this one, there is a newline.
-This is also true between non-code markup
- and code.
+
This is also true between non-code markup
+ and code.
Code can appear inside other markup. Its display
- shouldn't be affected.
+
Code can appear inside other markup
+ . Its display shouldn't be affected.
There is no differences between a b and a b.
Consecutive whitespaces not after a newline are conserved as they
- are: a b.
+
Consecutive whitespaces not after a newline are conserved as they are:
+ a b.
This is a link. It sends you to the top of this - page. Links can have markup inside them: bold - , italics, emphasis - , superscript, +
This is a link
+ . It sends you to the top of this page. Links can have markup inside
+ them: bold, italics
+ , emphasis,
+ superscript,
subscript, and
code. Links can also be nested
- inside markup. Links cannot be nested inside
- each other. This link has no replacement text: #
+ inside
+ markup. Links cannot be nested inside each other. This link has no
+ replacement text: #
. The text is filled in by odoc. This is a shorthand link:
#. The text is also filled in by odoc in this case.
This is a reference to foo.
- References can have replacement text:
- the value foo. Except for the
- special lookup support, references are pretty much just like links.
- The replacement text can have nested styles:
+
This is a reference to foo
+ . References can have replacement text:
+ the value foo
+ . Except for the special lookup support, references are pretty much just
+ like links. The replacement text can have nested styles:
bold,
italic,
emphasis,
superscript,
subscript, and
- code. It's also possible
- to surround a reference in a style:
- foo. References can't
- be nested inside references, and links and references can't be nested
- inside each other.
+ code
+ . It's also possible to surround a reference in a style:
+ foo
+ . References can't be nested inside references, and links and references
+ can't be nested inside each other.
just creates a paragraph outside the list.
The parser supports any ASCII-compatible encoding.
In particuλar UTF-8.
Raw HTML can be as inline - elements into sentences. +
Raw HTML can be + as inline elements into sentences.
@@ -257,27 +263,31 @@Math
diff --git a/test/generators/html/Module.html b/test/generators/html/Module.html index c060d3c2b6..5a67f02efe 100644 --- a/test/generators/html/Module.html +++ b/test/generators/html/Module.html @@ -21,8 +21,9 @@ - A much longer paragraph which will need to be wrapped and more - content and more content and some different content and we will - see what is does if we can see it +
+ A much longer paragraph which will need to be wrapped and more content + and more content and some different content and we will see what is + does if we can see it
- B much longer paragraph which will need to be wrapped and more - content and more content and some different content and we will - see what is does if we can see it +
+ B much longer paragraph which will need to be wrapped and more content + and more content and some different content and we will see what is + does if we can see it
- C much longer paragraph which will need to be wrapped and more - content and more content and some different content and we will - see what is does if we can see it +
+ C much longer paragraph which will need to be wrapped and more content + and more content and some different content and we will see what is + does if we can see it
- D much longer paragraph which will need to be wrapped and more - content and more content and some different content and we will - see what is does if we can see it +
+ D much longer paragraph which will need to be wrapped and more content + and more content and some different content and we will see what is + does if we can see it
Module
ModuleFoo.
val foo : unit
The module needs at least one signature item, otherwise a bug - causes the compiler to drop the module comment (above). See +
+ The module needs at least one signature item, otherwise a bug causes + the compiler to drop the module comment (above). See https://caml.inria.fr/mantis/view.php?id=7701 . diff --git a/test/generators/html/Ocamlary-module-type-SuperSig-module-type-SubSigA.html b/test/generators/html/Ocamlary-module-type-SuperSig-module-type-SubSigA.html index 0990a96216..6c28823ad0 100644 --- a/test/generators/html/Ocamlary-module-type-SuperSig-module-type-SubSigA.html +++ b/test/generators/html/Ocamlary-module-type-SuperSig-module-type-SubSigA.html @@ -26,8 +26,8 @@
SuperSig.SubSigASuperSig.SubSigBOcamlaryOcamlaryYou may find more information about this HTML documentation renderer - at +
You may find more information about this HTML documentation renderer at github.com/dsheets/ocamlary .
This is some verbatim text:
verbatim@@ -271,8 +270,8 @@
SuperSig.EmptySig
- . Section Section 9000 is
- also interesting. EmptySig
+ . Section Section 9000
+ is also interesting. EmptySig
is the section and
EmptySig
is the module signature.
@@ -296,8 +295,7 @@ Some text before exception title.
After exception title.
a_function is this
- type and a_function
+
a_function
+ is this type and a_function
is the value below.
With ocamldoc, toplevel units will be linked and documented, while - submodules will behave as simple references. +
+ With ocamldoc, toplevel units will be linked and documented, while + submodules will behave as simple references.
-With odoc, everything should be resolved (and linked) but only - toplevel units will be documented. +
+ With odoc, everything should be resolved (and linked) but only toplevel + units will be documented.
Dep1.X Ocamlary
This is an interface with all
- of the module system features. This documentation
- demonstrates:
+ of the module system
+ features. This documentation demonstrates:
Dep4.X I can refer to
{!section:indexmodules} :
- Trying the {!modules:
- ...} command.
+
+ Trying the {!modules: ...} command.
{!aliases} :
@@ -2832,8 +2831,7 @@ Oxcaml.M1uncontended and nonportable are the defaults
- (not rendered).
+
uncontended and nonportable
+ are the defaults (not rendered).
Oxcaml.M2Module with portable modality. The modality is applied
- to all value members of M2.
+
Module with portable
+ modality. The modality is applied to all value members of
+ M2.
Oxcaml.M3contended modality applied to all definitions in the
- module, except the ones which have already specified this axis.
+
contended
+ modality applied to all definitions in the module, except the ones which
+ have already specified this axis.
Oxcaml.Suncontended and nonportable are the defaults
- (not rendered).
+
uncontended and nonportable
+ are the defaults (not rendered).
Oxcamlwith constraints
+ Kind annotations with
+ with constraints
type t_value
value is the default kind, so the annotation is
- not rendered.
+
value
+ is the default kind, so the annotation is not rendered.
A with constraint whose right-hand side is a
- parameterized type constructor (as found in base's
- Map module).
+
A with
+ constraint whose right-hand side is a parameterized type constructor
+ (as found in base's Map module).
A with constraint whose right-hand side applies
- a type constructor.
+
A with
+ constraint whose right-hand side applies a type constructor.
A with constraint whose right-hand side is an arrow
- type.
+
A with
+ constraint whose right-hand side is an arrow type.
A with constraint whose right-hand side is an arrow
- type carrying a mode.
+
A with
+ constraint whose right-hand side is an arrow type carrying a mode.
A with constraint referring to a type through a
- module path (M.t).
+
A with
+ constraint referring to a type through a module path (M.t
+ ).
A with constraint referring to a type through a
- functor application (F(X).t).
+
A with
+ constraint referring to a type through a functor application (
+ F(X).t).
A with constraint whose right-hand side is a polymorphic
- variant.
+
A with
+ constraint whose right-hand side is a polymorphic variant.
A with constraint whose right-hand side is an object
- type.
+
A with
+ constraint whose right-hand side is an object type.
Zero allocation bindings have an extension attribute attached. - See +
+ Zero allocation bindings have an extension attribute attached. See https://oxcaml.org/documentation/miscellaneous-extensions/zero_alloc_check/
Module with portable modality. The modality is applied
- to all value members of M2.
+
Module with portable
+ modality. The modality is applied to all value members of
+ M2.
contended modality applied to all definitions in
- the module, except the ones which have already specified this
- axis.
+
contended
+ modality applied to all definitions in the module, except the ones
+ which have already specified this axis.
Same as mode_multi, to show that modes order is
- normalized.
+
Same as mode_multi
+ , to show that modes order is normalized.
Mode on a result that is itself an arrow. The arrow must be - parenthesized so the mode does not appear to bind to the inner - return type. +
+ Mode on a result that is itself an arrow. The arrow must be + parenthesized so the mode does not appear to bind to the inner return + type.
Closing over an argument constrains the partial-application closure - across several axes, not just locality. When the result mode is - the one currying implies from the argument, it is suppressed (as - the compiler does). +
+ Closing over an argument constrains the partial-application closure + across several axes, not just locality. When the result mode is the one + currying implies from the argument, it is suppressed (as the compiler + does).
once argument: the implied once result
- mode is suppressed.
+
once argument: the implied once
+ result mode is suppressed.
portable argument: the implied result mode is
- suppressed.
+
portable
+ argument: the implied result mode is suppressed.
contended argument: the implied result mode is
- suppressed.
+
contended
+ argument: the implied result mode is suppressed.
A result mode is only suppressed when it is exactly the one currying - implies. An explicit mode on a different axis is kept (and the arrow - result is parenthesized). +
+ A result mode is only suppressed when it is exactly the one currying + implies. An explicit mode on a different axis is kept (and the arrow + result is parenthesized).
The curry-implied once is suppressed, but the explicit
- portable is kept.
+
The curry-implied once is suppressed, but the explicit
+ portable is kept.
The curry-implied local is suppressed, but the explicit
- portable is kept.
+
The curry-implied local is suppressed, but the explicit
+ portable is kept.
The nonportable argument mode is the default and
- dropped, while the explicit portable result, not
- implied by currying, is kept.
+
The nonportable
+ argument mode is the default and dropped, while the explicit
+ portable result, not implied by currying, is kept.
Fork mode (identity on a non-local argument, not
- rendered).
+
Fork mode (identity on a non-local
+ argument, not rendered).
Statefulness mode (identity when portability is
- at its default, not rendered).
+
Statefulness mode (identity when portability
+ is at its default, not rendered).
Staticity mode (legacy, not rendered).
Some axes have a default value that is implied by another axis; - the implied value is suppressed when rendering. +
+ Some axes have a default value that is implied by another axis; the + implied value is suppressed when rendering.
yielding is the default for local,
- so it is not rendered.
+
yielding is the default for local
+ , so it is not rendered.
Oxcaml_implOxcaml_implSectionThis is the module comment. Eventually, sections won't be allowed - in it. +
+ This is the module comment. Eventually, sections won't be allowed in it.
Sectionsection title has markup
But links are impossible thanks to the parser, so we never have - trouble rendering a section title in a table of contents - no link - will be nested inside another link. +
+ But links are impossible thanks to the parser, so we never have trouble + rendering a section title in a table of contents - no link will be nested + inside another link.