diff --git a/CHANGES.md b/CHANGES.md index 479cfcaf6..9916e6c87 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,15 @@ ## Fixes +- Refactor comment parsing to use `odoc-parser` and `cmarkit` instead of + `octavius` and `omd` (#1088) + + This allows users who migrated to omd 2.X to install ocaml-lsp-server in the + same opam switch. + + We also slightly improved markdown generation support and fixed a couple in + the generation of inline heading and module types. + - Allow opening documents that were already open. This is a workaround for neovim's lsp client (#1067) diff --git a/dune-project b/dune-project index 8420d9c41..b8a87a2e4 100644 --- a/dune-project +++ b/dune-project @@ -56,11 +56,11 @@ possible and does not make any assumptions about IO. ordering dune-build-info spawn + (cmarkit (>= 0.2.0)) + (odoc-parser (>= 2.0.0)) (ppx_expect (and (>= v0.15.0) :with-test)) (ocamlformat (and :with-test (= 0.24.1))) (ocamlc-loc (and (>= 3.5.0) (< 3.7.0))) - (omd (and (>= 1.3.2) (< 2.0.0~alpha1))) - (octavius (>= 1.2.2)) (uutf (>= 1.0.2)) (pp (>= 1.1.2)) (csexp (>= 1.5)) diff --git a/flake.lock b/flake.lock index e12797907..3f8762109 100644 --- a/flake.lock +++ b/flake.lock @@ -142,11 +142,11 @@ "opam-repository": { "flake": false, "locked": { - "lastModified": 1682021363, - "narHash": "sha256-nDUDFwyOTZDALeqqEDnF2PTPIHT4sVYdQXUbRt03oNs=", + "lastModified": 1684354567, + "narHash": "sha256-e6Ohs0riGaIUtUt1vI29rNmeJxHJFe/cgG7IUmIcYNs=", "owner": "ocaml", "repo": "opam-repository", - "rev": "786c55fa77c37f07eceea7d6a9bec04d2225e302", + "rev": "8e3f9bbe3f849d3eff023b1cdb0d66069c93435c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index c22d616dc..a92dfbabc 100644 --- a/flake.nix +++ b/flake.nix @@ -76,8 +76,8 @@ duneVersion = "3"; buildInputs = with pkgs.ocamlPackages; [ ocamlc-loc - omd - octavius + odoc-parser + cmarkit dune-build-info re dune-rpc diff --git a/ocaml-lsp-server.opam b/ocaml-lsp-server.opam index 8ef8542f2..ecc301427 100644 --- a/ocaml-lsp-server.opam +++ b/ocaml-lsp-server.opam @@ -32,11 +32,11 @@ depends: [ "ordering" "dune-build-info" "spawn" + "cmarkit" {>= "0.2.0"} + "odoc-parser" {>= "2.0.0"} "ppx_expect" {>= "v0.15.0" & with-test} "ocamlformat" {with-test & = "0.24.1"} "ocamlc-loc" {>= "3.5.0" & < "3.7.0"} - "omd" {>= "1.3.2" & < "2.0.0~alpha1"} - "octavius" {>= "1.2.2"} "uutf" {>= "1.0.2"} "pp" {>= "1.1.2"} "csexp" {>= "1.5"} diff --git a/ocaml-lsp-server/src/doc_to_md.ml b/ocaml-lsp-server/src/doc_to_md.ml index 421636a34..a2a80a5d6 100644 --- a/ocaml-lsp-server/src/doc_to_md.ml +++ b/ocaml-lsp-server/src/doc_to_md.ml @@ -1,150 +1,294 @@ open Import -module Oct = Octavius -let ocaml = "ocaml" +open struct + open Cmarkit + module Inline = Inline + module Meta = Meta + module Block_line = Block_line + module Link_definition = Link_definition + module Block = Block + module Layout = Layout + module Doc = Doc +end -let to_inline_code code = Omd.Code (ocaml, code) +(** TODO: -let to_code_block code = Omd.Code_block (ocaml, code) + - Add support for references + - Labels in headers + - Align text with HTML + - Verbatim is indented in tests -let space = Omd.Text " " + Unsupported (next): -let new_line = Omd.NL + - Support meta from odoc-parser locations *) -(* [put_in_between elem lst] inserts [elem] between all elements of [lst] *) -let put_in_between elem lst = - let rec loop acc = function - | [] -> acc - | [ hd ] -> hd :: acc - | hd :: tail -> loop (elem :: hd :: acc) tail - in - List.rev (loop [] lst) - -let heading_level level heading : Omd.element = - match level with - | 0 -> H1 heading - | 1 -> H2 heading - | 2 -> H3 heading - | 3 -> H4 heading - | 4 -> H5 heading - | _ -> H6 heading - -let style_markdown (kind : Oct.Types.style_kind) md : Omd.element list = - match kind with - | SK_bold -> [ Bold md ] - | SK_italic | SK_emphasize -> [ Emph md ] - | SK_center | SK_left | SK_right | SK_superscript | SK_subscript | SK_custom _ - -> - (* TODO: implement SK_{center, left, right, superscript, subscript, custom} - using html blocks *) - md - -let rec text_to_markdown doc = List.concat_map ~f:text_element_to_markdown doc - -and text_element_to_markdown (doc_elem : Oct.Types.text_element) = - match doc_elem with - | Raw text -> [ Omd.Text text ] - | Code code -> [ to_inline_code code ] - | PreCode code -> [ to_code_block code ] - | Verbatim text -> [ Raw text ] - | Style (kind, text) -> style_markdown kind @@ text_to_markdown text - | List l -> [ Ul (text_elements_to_markdown l) ] - | Enum l -> [ Ol (text_elements_to_markdown l) ] - | Newline -> [ new_line; new_line ] - | Title (i, _, content) -> - (* TODO: along with cross-references support, add support for labels *) - let heading = text_to_markdown content in - [ heading_level i heading ] - | Ref (RK_link, url, descr) -> - let descr = - Option.map descr ~f:text_to_markdown |> Option.value ~default:[] +type t = + | Raw of string + | Markdown of string + +let loc_to_meta _loc = Meta.none + +let style_inline ~meta (style : Odoc_parser.Ast.style) inline = + match style with + | `Bold -> Inline.Strong_emphasis (Inline.Emphasis.make inline, meta) + | `Italic -> Inline.Emphasis (Inline.Emphasis.make inline, meta) + | `Emphasis -> Inline.Emphasis (Inline.Emphasis.make inline, meta) + | `Superscript -> inline + | `Subscript -> inline + +let rec inline_element_to_inline + (inline : Odoc_parser.Ast.inline_element Odoc_parser.Loc.with_location) : + Inline.t = + match inline with + | { value = `Space _; location } -> + let meta = loc_to_meta location in + Inline.Text (" ", meta) + | { value = `Word w; location } -> + let meta = loc_to_meta location in + Inline.Text (w, meta) + | { value = `Code_span c; location } -> + let meta = loc_to_meta location in + Inline.Code_span (Inline.Code_span.of_string c, meta) + | Odoc_parser.Loc.{ value = `Raw_markup (Some "html", text); location } -> + let meta = loc_to_meta location in + Inline.Raw_html (Block_line.tight_list_of_string text, meta) + | { value = `Raw_markup (_, text); location } -> + (* Cmarkit doesn't have constructors for backend other than HTML for inline + raw markups, only for blocks. *) + let meta = loc_to_meta location in + Inline.Text (text, meta) + | { value = `Styled (style, inlines); location } -> + let text = inline_element_list_to_inlines inlines in + let meta = loc_to_meta location in + style_inline ~meta style text + | { value = `Reference (_kind, _ref, _inlines); location = _location } -> + (* TODO: add support for references *) + Inline.Break (Inline.Break.make `Hard, Meta.none) + | { value = `Link (link, inlines); location } -> + let text = inline_element_list_to_inlines inlines in + let ref = + `Inline (Link_definition.make ~dest:(link, Meta.none) (), Meta.none) + in + let link = Inline.Link.make text ref in + let meta = loc_to_meta location in + Inline.Link (link, meta) + | { value = `Math_span text; location } -> + let meta = loc_to_meta location in + Inline.Ext_math_span + ( Inline.Math_span.make + ~display:false + (Block_line.tight_list_of_string text) + , meta ) + +and inline_element_list_to_inlines inlines = + let inlines = List.map ~f:inline_element_to_inline inlines in + Inline.Inlines (inlines, Meta.none) + +let rec nestable_block_element_to_block + (nestable : + Odoc_parser.Ast.nestable_block_element Odoc_parser.Loc.with_location) = + match nestable with + | { value = `Paragraph text; location } -> + let inline = inline_element_list_to_inlines text in + let paragraph = Block.Paragraph.make inline in + let meta = loc_to_meta location in + Block.Paragraph (paragraph, meta) + | { value = `List (kind, style, xs); location } -> + let type' = + match kind with + | `Unordered -> `Unordered '-' + | `Ordered -> `Ordered (1, '*') + in + let tight = + match style with + | `Heavy -> false + | `Light -> true + in + let list_items = + List.map xs ~f:(fun n -> + let block = nestable_block_element_list_to_block n in + (Block.List_item.make ~after_marker:1 block, Meta.none)) + in + let l = Block.List'.make ~tight type' list_items in + let meta = loc_to_meta location in + Block.List (l, meta) + | { value = `Modules modules; location } -> + let type' = `Unordered '*' in + let tight = false in + let list_items = + List.map modules ~f:(fun Odoc_parser.Loc.{ value = m; location } -> + let inline = Inline.Text (m, Meta.none) in + let paragraph = Block.Paragraph.make inline in + let block = Block.Paragraph (paragraph, Meta.none) in + let meta = loc_to_meta location in + let marker = Layout.string "!modules:" in + (Block.List_item.make ~after_marker:1 ~marker block, meta)) + in + let l = Block.List'.make ~tight type' list_items in + let meta = loc_to_meta location in + Block.List (l, meta) + | { value = `Code_block (metadata, { value = code; location = code_loc }) + ; location + } -> + let info_string = + match metadata with + | None -> Some ("ocaml", loc_to_meta code_loc) + | Some ({ value = lang; location = lang_log }, _env) -> + Some (lang, loc_to_meta lang_log) in - let empty_title = "" in - [ Url (url, descr, empty_title) ] - | Ref (_ref_kind, reference, descr) -> - (* TODO: add support for cross-references *) - Option.map ~f:text_to_markdown descr - |> Option.value ~default:[ to_inline_code reference ] - | Special_ref _ - | Target (_, _) (* TODO: add support for markdown-specific blocks *) -> [] - -and text_elements_to_markdown lst = List.map ~f:text_to_markdown lst - -let rec tags_to_markdown (tags : Oct.Types.tag list) = - List.map ~f:tag_to_markdown tags - |> put_in_between [ new_line; new_line ] - |> List.concat - -and tag_to_markdown tag : Omd.element list = - let format_tag tag = Omd.Bold [ Emph [ Text tag ] ] in - let plain_tag_to_md tag descr = [ format_tag tag; space; Text descr ] in - let tag_with_text_to_md tag text = - format_tag tag :: space :: text_to_markdown text + let block_line = Block_line.list_of_string code in + let code_block = Block.Code_block.make ?info_string block_line in + let meta = loc_to_meta location in + Block.Code_block (code_block, meta) + | { value = `Verbatim code; location } -> + let info_string = Some ("verb", Meta.none) in + let block_line = Block_line.list_of_string code in + let code_block = Block.Code_block.make ?info_string block_line in + let meta = loc_to_meta location in + Block.Code_block (code_block, meta) + | { value = `Math_block code; location } -> + let block_line = Block_line.list_of_string code in + let code_block = Block.Code_block.make block_line in + let meta = loc_to_meta location in + Block.Ext_math_block (code_block, meta) + +and nestable_block_element_list_to_block nestables = + let blocks = List.map ~f:nestable_block_element_to_block nestables in + Block.Blocks (blocks, Meta.none) + +let strong_and_emphasis s = + Inline.Emphasis + ( Inline.Emphasis.make + (Inline.Strong_emphasis + (Inline.Emphasis.make (Inline.Text (s, Meta.none)), Meta.none)) + , Meta.none ) + +let inline_code_span_of_string s = + Inline.Code_span + ( Inline.Code_span.make ~backtick_count:1 (Block_line.tight_list_of_string s) + , Meta.none ) + +let inline_link_of_string ~text uri = + let ref = + `Inline (Link_definition.make ~dest:(uri, Meta.none) (), Meta.none) in - let marked_tag_to_md tag mark = - [ format_tag tag; space; to_inline_code mark ] + Inline.Link (Inline.Link.make (Inline.Text (text, Meta.none)) ref, Meta.none) + +let tag_to_block ~meta (tag : Odoc_parser.Ast.tag) = + let format_tag_empty tag = + Block.Paragraph (Block.Paragraph.make (strong_and_emphasis tag), Meta.none) in - let marked_tag_with_text_to_md tag mark text = - format_tag tag :: space :: to_inline_code mark :: space - :: text_to_markdown text + let format_tag_string tag text = + let inline = + Inline.Inlines + ( [ strong_and_emphasis tag; Inline.Text (" ", Meta.none); text ] + , Meta.none ) + in + Block.Paragraph (Block.Paragraph.make inline, meta) in - let see_tag_to_md (see_ref, comment) = - let content = - match (see_ref, comment) with - | Oct.Types.See_url url, text -> - let empty_hover_title = "" in - let link_title = [ Omd.Text "link" ] in - Omd.Url (url, link_title, empty_hover_title) - :: space :: text_to_markdown text - | See_file name, text | See_doc name, text -> - let no_prog_lang = "" in - (* TODO: add support to reference files and documents *) - Code (no_prog_lang, name) :: space :: text_to_markdown text + let format_tag_block tag block = + let prefix = + Block.Paragraph (Block.Paragraph.make (strong_and_emphasis tag), Meta.none) in - format_tag "@see" :: space :: content + Block.Blocks ([ prefix; block ], meta) + in + let format_tag_string_with_block tag text block = + let prefix = + let inline = + Inline.Inlines + ( [ strong_and_emphasis tag; Inline.Text (" ", Meta.none); text ] + , Meta.none ) + in + Block.Paragraph (Block.Paragraph.make inline, Meta.none) + in + Block.Blocks ([ prefix; block ], meta) in match tag with - | Author a -> plain_tag_to_md "@author" a - | Canonical c -> plain_tag_to_md "@canonical" c - | Deprecated text -> tag_with_text_to_md "@deprecated" text - | Return_value text -> tag_with_text_to_md "@return" text - | Version v -> marked_tag_to_md "@version" v - | Since v -> marked_tag_to_md "@since" v - | Before (ver, text) -> ( - match text with - | [] -> marked_tag_to_md "@before" ver - | _ -> marked_tag_with_text_to_md "@before" ver text) - | Param (name, text) -> ( - match text with - | [] -> - tag_with_text_to_md "@param" [ Raw name ] (* in case `id` is missing) *) - | _ -> marked_tag_with_text_to_md "@param" name text) - | Raised_exception (exn, text) -> marked_tag_with_text_to_md "@raise" exn text - | See (r, s) -> see_tag_to_md (r, s) - | Custom (name, text) -> - [ Omd.Emph [ Text ("@" ^ name) ]; space ] @ text_to_markdown text - | Inline -> [ Emph [ Text "@inline" ] ] - -let comment_to_markdown (doc, tags) = - let text = text_to_markdown doc in - let tags = tags_to_markdown tags in - match tags with - | [] -> text - | non_empty_tags -> - let separation = Omd.[ NL; Hr ] in - text @ separation @ non_empty_tags + | `Author s -> + let s = Inline.Text (s, Meta.none) in + format_tag_string "@author" s + | `Deprecated text -> + let block = nestable_block_element_list_to_block text in + format_tag_block "@deprecated" block + | `Param (id, []) -> + let id = Inline.Text (id, Meta.none) in + format_tag_string "@param" id + | `Param (id, text) -> + let block = nestable_block_element_list_to_block text in + let id = inline_code_span_of_string id in + format_tag_string_with_block "@param" id block + | `Raise (exc, text) -> + let block = nestable_block_element_list_to_block text in + let exc = inline_code_span_of_string exc in + format_tag_string_with_block "@raise" exc block + | `Return text -> + let block = nestable_block_element_list_to_block text in + format_tag_block "@return" block + | `See (`Url, uri, text) -> + let block = nestable_block_element_list_to_block text in + let uri = inline_link_of_string ~text:"link" uri in + format_tag_string_with_block "@see" uri block + | `See ((`File | `Document), uri, text) -> + let block = nestable_block_element_list_to_block text in + let uri = inline_code_span_of_string uri in + format_tag_string_with_block "@see" uri block + | `Since version -> + let version = inline_code_span_of_string version in + format_tag_string "@since" version + | `Before (version, text) -> + let block = nestable_block_element_list_to_block text in + let version = inline_code_span_of_string version in + format_tag_string_with_block "@before" version block + | `Version version -> + let version = inline_code_span_of_string version in + format_tag_string "@version" version + | `Canonical { value = s; location = _ } -> + let s = Inline.Text (s, Meta.none) in + format_tag_string "@canonical" s + | `Inline -> format_tag_empty "@inline" + | `Open -> format_tag_empty "@open" + | `Closed -> format_tag_empty "@closed" -type t = - | Raw of string - | Markdown of string +let rec block_element_to_block + (block_element : + Odoc_parser.Ast.block_element Odoc_parser.Loc.with_location) = + match block_element with + | { value = `Heading (level, _, content); location } -> + let text = inline_element_list_to_inlines content in + let heading = Block.Heading.make ~level:(level + 1) text in + let meta = loc_to_meta location in + Block.Heading (heading, meta) + | { value = `Tag t; location } -> + let meta = loc_to_meta location in + tag_to_block ~meta t + | { value = + ( `Paragraph _ + | `List _ + | `Modules _ + | `Code_block _ + | `Verbatim _ + | `Math_block _ ) + ; location = _ + } as nestable -> nestable_block_element_to_block nestable + +and block_element_list_to_block l = + let rec aux acc rest = + match rest with + | [] -> List.rev acc + | el :: [] -> List.rev (block_element_to_block el :: acc) + | el :: rest -> + aux + (Block.Blank_line ("", Meta.none) :: block_element_to_block el :: acc) + rest + in + let blocks = aux [] l in + Block.Blocks (blocks, Meta.none) let translate doc : t = - match Oct.parse (Lexing.from_string doc) with - | Error e -> - let msg = Oct.Errors.message e.error in - Log.log ~section:"debug" (fun () -> - Log.msg "invalid doc comments" [ ("msg", `String msg) ]); - Raw (Omd.to_markdown [ Raw doc ]) - | Ok doc -> - let doc = comment_to_markdown doc in - Markdown (Omd.to_markdown doc) + let location = Lexing.dummy_pos in + let v = Odoc_parser.parse_comment ~location ~text:doc in + let ast = Odoc_parser.ast v in + let block = block_element_list_to_block ast in + let doc = Doc.make block in + let cmark = Cmarkit_commonmark.of_doc doc in + Markdown cmark diff --git a/ocaml-lsp-server/src/dune b/ocaml-lsp-server/src/dune index 6f0584538..c62a1507e 100644 --- a/ocaml-lsp-server/src/dune +++ b/ocaml-lsp-server/src/dune @@ -24,8 +24,8 @@ merlin-lib.ocaml_utils merlin-lib.utils merlin-lib.extend - octavius - omd + cmarkit + odoc-parser ppx_yojson_conv_lib re stdune diff --git a/ocaml-lsp-server/test/e2e/__tests__/ocamllsp-hoverExtended.ts b/ocaml-lsp-server/test/e2e/__tests__/ocamllsp-hoverExtended.ts index 142c58364..52241e90f 100644 --- a/ocaml-lsp-server/test/e2e/__tests__/ocamllsp-hoverExtended.ts +++ b/ocaml-lsp-server/test/e2e/__tests__/ocamllsp-hoverExtended.ts @@ -219,24 +219,30 @@ describe("ocamllsp/hoverExtended", () => { This function has a nice documentation. It performs division of two integer numbers. - * * * - ***@param*** \`x\` dividend + + ***@param*** \`x\` + dividend ***@param*** divisor - ***@return*** *quotient*, i.e. result of division + ***@return*** + *quotient*, i.e. result of division - ***@raise*** \`Division_by_zero\` raised when divided by zero + ***@raise*** \`Division_by_zero\` + raised when divided by zero - ***@see*** [link](https://en.wikipedia.org/wiki/Arithmetic#Division_(%C3%B7,_or_/)) article + ***@see*** [link](https://en.wikipedia.org/wiki/Arithmetic#Division_\\(%C3%B7,_or_/\\)) + article - ***@see*** \`arithmetic.ml\` for more context + ***@see*** \`arithmetic.ml\` + for more context ***@since*** \`4.0.0\` ***@before*** \`4.4.0\` - ***@deprecated*** use \`(/)\` + ***@deprecated*** + use \`(/)\` ***@version*** \`1.0.0\` diff --git a/ocaml-lsp-server/test/e2e/__tests__/textDocument-hover.test.ts b/ocaml-lsp-server/test/e2e/__tests__/textDocument-hover.test.ts index b2534b49a..0484b048d 100644 --- a/ocaml-lsp-server/test/e2e/__tests__/textDocument-hover.test.ts +++ b/ocaml-lsp-server/test/e2e/__tests__/textDocument-hover.test.ts @@ -258,24 +258,30 @@ describe("textDocument/hover", () => { This function has a nice documentation. It performs division of two integer numbers. - * * * - ***@param*** \`x\` dividend + + ***@param*** \`x\` + dividend ***@param*** divisor - ***@return*** *quotient*, i.e. result of division + ***@return*** + *quotient*, i.e. result of division - ***@raise*** \`Division_by_zero\` raised when divided by zero + ***@raise*** \`Division_by_zero\` + raised when divided by zero - ***@see*** [link](https://en.wikipedia.org/wiki/Arithmetic#Division_(%C3%B7,_or_/)) article + ***@see*** [link](https://en.wikipedia.org/wiki/Arithmetic#Division_\\(%C3%B7,_or_/\\)) + article - ***@see*** \`arithmetic.ml\` for more context + ***@see*** \`arithmetic.ml\` + for more context ***@since*** \`4.0.0\` ***@before*** \`4.4.0\` - ***@deprecated*** use \`(/)\` + ***@deprecated*** + use \`(/)\` ***@version*** \`1.0.0\` diff --git a/ocaml-lsp-server/test/e2e/__tests__/textDocument-signatureHelp.ts b/ocaml-lsp-server/test/e2e/__tests__/textDocument-signatureHelp.ts index c6d0f7bd5..80c598a69 100644 --- a/ocaml-lsp-server/test/e2e/__tests__/textDocument-signatureHelp.ts +++ b/ocaml-lsp-server/test/e2e/__tests__/textDocument-signatureHelp.ts @@ -285,6 +285,11 @@ describe_opt("textDocument/completion", () => { let square x = x * x let result = square 3 ]} + + {@python[ + def f(): + return 0 + ]} {3 Verbatim} @@ -296,7 +301,7 @@ describe_opt("textDocument/completion", () => { {3 Module List} {!modules: Array List String} - + @param x dividend @param divisor @@ -338,11 +343,16 @@ describe_opt("textDocument/completion", () => { documentation: { kind: "markdown", value: outdent`This is an example of a docstring that demonstrates various ocamldoc syntax features. - + #### Sections and Labels - We can create sections using #### Section title - and labels using #### Section title with label + We can create sections using + + #### Section title + + and labels using + + #### Section title with label #### Links and Cross-references @@ -353,57 +363,75 @@ describe_opt("textDocument/completion", () => { **Bold**, *Italic*, *Emphasize*, Superscript, Subscript, and \`inline code\` #### Text Alignment - + Centered text + Left-aligned text + Right-aligned text #### Lists 1. Ordered list item 1 2. Ordered list item 2 - + - Unordered list item 1 - Unordered list item 2 - + - Unordered list item 1 - Unordered list item 2 #### Code Blocks \`\`\`ocaml - let square x = x * x - let result = square 3 + let square x = x * x + let result = square 3 \`\`\` - + + \`\`\`python + def f(): + return 0 + \`\`\` + #### Verbatim + \`\`\`verb This text will be displayed verbatim. No formatting will be applied. + \`\`\` #### Module List - * * * - ***@param*** \`x\` dividend + * Array + * List + * String + ***@param*** \`x\` + dividend + ***@param*** divisor - - ***@return*** *quotient*, i.e. result of division - - ***@raise*** \`Division_by_zero\` raised when divided by zero - - ***@see*** [link](https://en.wikipedia.org/wiki/Arithmetic#Division_(%C3%B7,_or_/)) article - - ***@see*** \`arithmetic.ml\` for more context - + + ***@return*** + *quotient*, i.e. result of division + + ***@raise*** \`Division_by_zero\` + raised when divided by zero + + ***@see*** [link](https://en.wikipedia.org/wiki/Arithmetic#Division_\\(%C3%B7,_or_/\\)) + article + + ***@see*** \`arithmetic.ml\` + for more context + ***@since*** \`4.0.0\` - + ***@before*** \`4.4.0\` - - ***@deprecated*** use \`(/)\` - + + ***@deprecated*** + use \`(/)\` + ***@version*** \`1.0.0\` - + ***@author*** John Doe `, },