Skip to content

Commit

Permalink
Add a custom "Output_sink" module for emitting strings
Browse files Browse the repository at this point in the history
OCaml doesn't have a good abstraction over writing text to generic
output (file, stdout, to memory). As a result, we often end up using
Format.formatter as an arbitrary output source.

This is fine in some use-cases. However, Format is designed around
pretty-printing values, rather than being high-performance. We see this
particularly when emitting HTML, with the format machinery contributing
a significant amount of time.

This change adds a new Output_sink module, which effectively exposes a
single "write" method. We switch the HTML module over to this.

This provides a significant performance boost. For a CC:T doc-gen this
reduces allocations and time taken by ~15%.
  • Loading branch information
SquidDev committed Oct 21, 2024
1 parent 43ee16c commit f1551d5
Show file tree
Hide file tree
Showing 28 changed files with 311 additions and 232 deletions.
1 change: 0 additions & 1 deletion src/bin/cli/dune
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
illuaminate.lint
illuaminate.config
illuaminateConfigFormat
illuaminate.html
illuaminate.minify
illuaminate.pattern
illuaminate.doc_emit
Expand Down
7 changes: 2 additions & 5 deletions src/bin/cli/illuaminate_cli.ml
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,7 @@ let doc_gen path =
let to_abs path = Fpath.to_string (to_abs' path) in

(* Write a HTML doc to a file. *)
let emit_doc node out =
let fmt = Format.formatter_of_out_channel out in
Html.Default.emit_doc fmt node; Format.pp_print_flush fmt ()
in
let emit_doc node out = Output_sink.with_output_stream out @@ fun out -> Html.emit_doc out node in

(* Resolve the path to the logo, copying it into the output directory if needed. *)
let resolve_logo ~data ~destination logo =
Expand Down Expand Up @@ -199,7 +196,7 @@ let doc_gen path =
|> CCIO.with_out ~flags:[ Open_creat; Open_trunc; Open_binary ] (Fpath.to_string path) );

let path = Fpath.(destination / "index.html") in
Option.fold ~none:Html.Default.nil ~some:(parse_index ~options:(options Fun.id)) index
Option.fold ~none:Html.nil ~some:(parse_index ~options:(options Fun.id)) index
|> E.Html.emit_index ~options:(options Fun.id) ~pages
|> emit_doc
|> CCIO.with_out ~flags:[ Open_creat; Open_trunc; Open_binary ] (Fpath.to_string path);
Expand Down
1 change: 0 additions & 1 deletion src/doc_emit/dune
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
illuaminate
illuaminate.core
illuaminate.data
illuaminate.html
illuaminate.parser
illuaminate.semantics
markup
Expand Down
7 changes: 4 additions & 3 deletions src/doc_emit/html_basic.ml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ let reference_link ~options:{ resolve; _ } : Reference.resolved -> string option
| External { url = None; _ } -> None
| Unknown _ -> None

let show_list ?(tag = "h3") ?(expandable = false) ?(expand = true) title = function
| [] -> Html.Default.nil
let show_list ?(tag = "h3") ?(expandable = false) ?(expand = true) title =
let open Illuaminate.Html in
function
| [] -> nil
| xs ->
let open Html.Default in
[ create_node ~tag
~children:[ str title ]
~attributes:
Expand Down
12 changes: 8 additions & 4 deletions src/doc_emit/html_highlight.ml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let transform_ref ~options ((r : M.Reference.t), t) =
| _ -> None

let emit ~options ~data ~input visit tree =
let open Html.Default in
let open Illuaminate.Html in
(* TODO: Emit a true HTML node. Not sure how to do that elegantly though - we'd probably need to
use a visitor within Emit instead. *)
let res = Buffer.create (String.length input) in
Expand Down Expand Up @@ -49,7 +49,11 @@ let emit ~options ~data ~input visit tree =
in
("title", desc) :: attrs
in
Format.asprintf "<a%a>" Html.Emitters.attrs attrs
let module Sink = Illuaminate.Output_sink in
Sink.with_to_str @@ fun out ->
Sink.write out "<a";
Illuaminate.Html.emit_attrs out attrs;
Sink.write out ">"
| None ->
stack := false :: xs;
""
Expand Down Expand Up @@ -117,7 +121,7 @@ let do_lua ~options:({ Html_options.data; _ } as options) input =
| Error _, (lazy (Ok tree)) ->
let data = resolve tree in
(emit ~options ~data ~input Emit.program tree, Some `Stmt)
| Error _, (lazy (Error _)) -> (Html.Default.str input, None)
| Error _, (lazy (Error _)) -> (Illuaminate.Html.str input, None)

let lua ~options input = do_lua ~options input |> fst

Expand All @@ -129,6 +133,6 @@ let lua_block ?(attrs = []) ~options input =
| Some `Expr -> Some "expr"
| Some `Stmt -> Some "stmt"
in
Html.Default.create_node ~tag:"pre"
Illuaminate.Html.create_node ~tag:"pre"
~attributes:(("class", Some "highlight") :: ("data-lua-kind", kind) :: attrs)
~children:[ highlighted ] ()
4 changes: 2 additions & 2 deletions src/doc_emit/html_highlight.mli
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(** Highlight a Lua string, rendering it as HTML *)
val lua : options:Html_options.t -> string -> Html.Default.node
val lua : options:Html_options.t -> string -> Illuaminate.Html.node_

val lua_block :
?attrs:(string * string option) list -> options:Html_options.t -> string -> Html.Default.node
?attrs:(string * string option) list -> options:Html_options.t -> string -> Illuaminate.Html.node_
2 changes: 1 addition & 1 deletion src/doc_emit/html_loader.ml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
let load_file ~options path =
let open Html.Default in
let open Illuaminate.Html in
match CCIO.File.read (Fpath.to_string path) with
| Error msg ->
Format.asprintf "Cannot open documentation index '%a' (%s)\n%!" Fpath.pp path msg
Expand Down
2 changes: 1 addition & 1 deletion src/doc_emit/html_loader.mli
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
(** Load a file, converting it to a HTML node depending on the file's type. *)
val load_file : options:Html_options.t -> Fpath.t -> (Html.Default.node, string) result
val load_file : options:Html_options.t -> Fpath.t -> (Illuaminate.Html.node_, string) result
2 changes: 1 addition & 1 deletion src/doc_emit/html_main.re
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
open Html.Default;
open Illuaminate.Html;
open Html_basic;
open Html_md;
open Html_value;
Expand Down
6 changes: 3 additions & 3 deletions src/doc_emit/html_main.rei
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ type page_list :=

/** Emit an index file from a list of page. */
let emit_index:
(~options: Html_options.t, ~pages: page_list, Html.Default.node) =>
Html.Default.node;
(~options: Html_options.t, ~pages: page_list, Illuaminate.Html.node_) =>
Illuaminate.Html.node_;

/** Emit a single page. */
let emit_page:
(~options: Html_options.t, ~pages: page_list, documented(page)) =>
Html.Default.node;
Illuaminate.Html.node_;
5 changes: 3 additions & 2 deletions src/doc_emit/html_md.ml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
open Html.Default
open Illuaminate.Html
open Cmarkit
module S = IlluaminateSemantics.Doc.Syntax
module A = IlluaminateSemantics.Doc.AbstractSyntax
Expand Down Expand Up @@ -132,7 +132,8 @@ let emit_code_block ~options c block =
match language with
| "lua" ->
let attrs = merge_classes ~classes ~attrs in
Cmarkit_ext.cprintf c "%a" Html.Default.emit (Html_highlight.lua_block ~attrs ~options code)
let sink = Illuaminate.Output_sink.of_buffer (Cmarkit_renderer.Context.buffer c) in
Illuaminate.Html.emit sink (Html_highlight.lua_block ~attrs ~options code)
| _ ->
C.string c {|<pre|};
(match (language, classes) with
Expand Down
9 changes: 5 additions & 4 deletions src/doc_emit/html_md.mli
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
open IlluaminateSemantics

(** Render a markdown document to a HTML node.*)
val md : ?path:Fpath.t -> options:Html_options.t -> Doc.Syntax.Markdown.t -> Html.Default.node
val md : ?path:Fpath.t -> options:Html_options.t -> Doc.Syntax.Markdown.t -> Illuaminate.Html.node_

(** Render a description to a HTML node. *)
val show_desc : options:Html_options.t -> Doc.Syntax.description option -> Html.Default.node
val show_desc : options:Html_options.t -> Doc.Syntax.description option -> Illuaminate.Html.node_

(** Render a description to a HTML node. If this description is a single paragraph, it will be
rendered inline rather than wrapped in a [<p>] element. *)
val show_desc_inline : options:Html_options.t -> Doc.Syntax.description option -> Html.Default.node
val show_desc_inline :
options:Html_options.t -> Doc.Syntax.description option -> Illuaminate.Html.node_

(** Show the summary ({!Helpers.get_summary}) of a document. *)
val show_summary : options:Html_options.t -> Doc.Syntax.description option -> Html.Default.node
val show_summary : options:Html_options.t -> Doc.Syntax.description option -> Illuaminate.Html.node_
2 changes: 1 addition & 1 deletion src/doc_emit/html_type.re
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
open Html.Default;
open Illuaminate.Html;
open! IlluaminateSemantics.Doc.Syntax.Type;

let show_opt = (~kind, optional) =>
Expand Down
12 changes: 7 additions & 5 deletions src/doc_emit/html_type.rei
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
open Html.Default;
open IlluaminateSemantics;

/** Show an optional specifier if required. */
let show_opt: (~kind: string, bool) => node;
let show_opt: (~kind: string, bool) => Illuaminate.Html.node_;

/** Check if a type is optional (i.e. is a union contianing `nil`), and extract the non-optional type component if so. */
let opt_ty: Doc.Syntax.Type.t => (bool, Doc.Syntax.Type.t);

/** Convert a type to HTML, using some resolve function to look up internal links. */
let show_type: (~options: Html_options.t, Doc.Syntax.Type.t) => node;
let show_type:
(~options: Html_options.t, Doc.Syntax.Type.t) => Illuaminate.Html.node_;

/** Convert a potential type to HTML. */
let show_type_opt:
(~options: Html_options.t, option(Doc.Syntax.Type.t)) => node;
(~options: Html_options.t, option(Doc.Syntax.Type.t)) =>
Illuaminate.Html.node_;

/** Wrap a HTML node with a link to a reference, using some resolve function to look up internal links. */
let show_reference:
(~options: Html_options.t, Reference.resolved, node) => node;
(~options: Html_options.t, Reference.resolved, Illuaminate.Html.node_) =>
Illuaminate.Html.node_;
2 changes: 1 addition & 1 deletion src/doc_emit/html_value.re
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
open Html.Default;
open Illuaminate.Html;
open Html_basic;
open Html_md;
open Html_type;
Expand Down
14 changes: 9 additions & 5 deletions src/doc_emit/illuaminateDocEmit.mli
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ module Html : sig

module Highlight : sig
(** Highlight a Lua string, rendering it as HTML *)
val lua : options:Html_options.t -> string -> Html.Default.node
val lua : options:Html_options.t -> string -> Illuaminate.Html.node_

val lua_block :
?attrs:(string * string option) list -> options:Html_options.t -> string -> Html.Default.node
?attrs:(string * string option) list ->
options:Html_options.t ->
string ->
Illuaminate.Html.node_
end

module Assets = Html_assets
Expand All @@ -33,18 +36,19 @@ module Html : sig
Map.Make(IlluaminateSemantics.Namespace).t

(** Emit an index file from a list of pages. *)
val emit_index : options:Options.t -> pages:page_list -> Html.Default.node -> Html.Default.node
val emit_index :
options:Options.t -> pages:page_list -> Illuaminate.Html.node_ -> Illuaminate.Html.node_

(** Emit a single page. *)
val emit_page :
options:Options.t ->
pages:page_list ->
Doc.Syntax.page Doc.Syntax.documented ->
Html.Default.node
Illuaminate.Html.node_

(** Load a file and convert it to HTML. This correctly handles loading markdown, HTML and text
files. *)
val load_file : options:Options.t -> Fpath.t -> (Html.Default.node, string) result
val load_file : options:Options.t -> Fpath.t -> (Illuaminate.Html.node_, string) result

(** The contents of the default JS file. *)
val embedded_js : string
Expand Down
7 changes: 0 additions & 7 deletions src/html/dune

This file was deleted.

106 changes: 0 additions & 106 deletions src/html/html.ml

This file was deleted.

Loading

0 comments on commit f1551d5

Please sign in to comment.