1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
|
(**************************************************************************)
(* *)
(* OCamlFormat *)
(* *)
(* Copyright (c) Facebook, Inc. and its affiliates. *)
(* *)
(* This source code is licensed under the MIT license found in *)
(* the LICENSE file in the root directory of this source tree. *)
(* *)
(**************************************************************************)
(** Formatting combinators *)
(** Define the core type and minimal combinators.
Other higher level functions like [fmt_if] or [list_pn] are implemented
on top of this. The goal is to be able to modify the underlying
abstraction without modifying too many places in the [Fmt] module. *)
module T : sig
type t
val ( $ ) : t -> t -> t
(** Sequence *)
val with_pp : (Format_.formatter -> unit) -> t
(** Use an arbitrary pretty-printing function *)
val protect : t -> on_error:(exn -> unit) -> t
(** Exception handler *)
val lazy_ : (unit -> t) -> t
(** Defer the evaluation of some side effects until formatting happens.
This can matter if for example a list of [t] is built, and then only
some of them end up being displayed. Using [lazy_] ensures that only
side effects for the displayed elements have run.
See [tests_lazy] in [Test_fmt]. *)
val eval : Format_.formatter -> t -> unit
(** Main function to evaluate a term using an actual formatter. *)
end = struct
type t = (Format_.formatter -> unit) Staged.t
let ( $ ) f g =
let f = Staged.unstage f in
let g = Staged.unstage g in
Staged.stage (fun x -> f x ; g x)
let with_pp f = Staged.stage f
let eval fs f =
let f = Staged.unstage f in
f fs
let protect t ~on_error =
let t = Staged.unstage t in
Staged.stage (fun fs ->
try t fs
with exn ->
Format_.pp_print_flush fs () ;
on_error exn )
let lazy_ f =
Staged.stage (fun fs ->
let k = Staged.unstage (f ()) in
k fs )
end
include T
type sp = Blank | Cut | Space | Break of int * int
let ( >$ ) f g x = f $ g x
let set_margin n =
with_pp (fun fs ->
Format_.pp_set_geometry fs ~max_indent:n ~margin:(n + 1) )
let max_indent = ref None
let set_max_indent x = with_pp (fun _ -> max_indent := x)
(** Debug of formatting -------------------------------------------------*)
let with_box_debug k = with_pp (Box_debug.with_box (fun fs -> eval fs k))
(** Break hints and format strings --------------------------------------*)
let break n o =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
Box_debug.break fs n o ~stack ;
Format_.pp_print_break fs n o )
let force_break = break 1000 0
let space_break =
(* a stack is useless here, this would require adding a unit parameter *)
with_pp (fun fs ->
Box_debug.space_break fs ;
Format_.pp_print_space fs () )
let cut_break =
with_pp (fun fs ->
Box_debug.cut_break fs ;
Format_.pp_print_cut fs () )
let force_newline =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
Box_debug.force_newline ~stack fs ;
Format_.pp_force_newline fs () )
let cbreak ~fits ~breaks =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
Box_debug.cbreak fs ~stack ~fits ~breaks ;
Format_.pp_print_custom_break fs ~fits ~breaks )
let noop = with_pp (fun _ -> ())
let sequence l =
let rec go l len =
match l with
| [] -> noop
| [x] -> x
| l ->
let a_len = len / 2 in
let b_len = len - a_len in
let a, b = List.split_n l a_len in
go a a_len $ go b b_len
in
go l (List.length l)
(** Primitive types -----------------------------------------------------*)
let char c = with_pp (fun fs -> Format_.pp_print_char fs c)
let str_length s =
Uuseg_string.fold_utf_8 `Grapheme_cluster (fun n _ -> n + 1) 0 s
let str_as n s =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
Box_debug.start_str fs s ;
Format_.pp_print_as fs n s ;
Box_debug.end_str ~stack fs )
let str s = if String.is_empty s then noop else str_as (str_length s) s
let sp = function
| Blank -> char ' '
| Cut -> cut_break
| Space -> space_break
| Break (x, y) -> break x y
(** Primitive containers ------------------------------------------------*)
let opt o f = Option.value_map ~default:noop ~f o
let list_pn x1N pp =
match x1N with
| [] -> noop
| [x1] -> lazy_ (fun () -> pp ~prev:None x1 ~next:None)
| x1 :: (x2 :: _ as x2N) ->
let l =
let rec aux (prev, acc) = function
| [] -> acc
| [xI] -> aux (xI, (Some prev, xI, None) :: acc) []
| xI :: (xJ :: _ as xJN) ->
aux (xI, (Some prev, xI, Some xJ) :: acc) xJN
in
aux (x1, [(None, x1, Some x2)]) x2N
in
List.rev_map l ~f:(fun (prev, x, next) ->
lazy_ (fun () -> pp ~prev x ~next) )
|> sequence
let list_fl xs pp =
list_pn xs (fun ~prev x ~next ->
pp ~first:(Option.is_none prev) ~last:(Option.is_none next) x )
let list l sep f =
list_fl l (fun ~first:_ ~last x -> f x $ if last then noop else sep)
(** Conditional formatting ----------------------------------------------*)
let fmt_if cnd x = if cnd then x else noop
let fmt_or cnd t f = if cnd then t else f
let fmt_opt o = Option.value o ~default:noop
(** Conditional on immediately following a line break -------------------*)
let if_newline s =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
Box_debug.if_newline fs ~stack s ;
Format_.pp_print_string_if_newline fs s )
let break_unless_newline n o =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
Box_debug.break_unless_newline fs ~stack n o ;
Format_.pp_print_or_newline fs n o "" "" )
(** Conditional on breaking of enclosing box ----------------------------*)
type behavior = Fit | Break
let fits_or_breaks ~level fits nspaces offset breaks =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
Box_debug.fits_or_breaks fs ~stack fits nspaces offset breaks ;
Format_.pp_print_fits_or_breaks fs ~level fits nspaces offset breaks )
let fits_breaks ?force ?(hint = (0, Int.min_value)) ?(level = 0) fits breaks
=
let nspaces, offset = hint in
match force with
| Some Fit -> str fits
| Some Break -> fmt_if (offset >= 0) (break nspaces offset) $ str breaks
| None -> fits_or_breaks ~level fits nspaces offset breaks
let fits_breaks_if ?force ?hint ?level cnd fits breaks =
fmt_if cnd (fits_breaks ?force ?hint ?level fits breaks)
(** Wrapping ------------------------------------------------------------*)
let wrap_if cnd pre suf k = fmt_if cnd pre $ k $ fmt_if cnd suf
let wrap x = wrap_if true x
let wrap_if_fits_or cnd pre suf k =
if cnd then wrap (str pre) (str suf) k
else fits_breaks pre "" $ k $ fits_breaks suf ""
let wrap_fits_breaks_if ?(space = true) (c : Conf.t) cnd pre suf k =
match (c.fmt_opts.indicate_multiline_delimiters.v, space) with
| `No, false -> wrap_if cnd (str pre) (str suf) k
| `Space, _ | `No, true ->
fits_breaks_if cnd pre (pre ^ " ")
$ k
$ fits_breaks_if cnd suf ~hint:(1, 0) suf
| `Closing_on_separate_line, _ ->
fits_breaks_if cnd pre (pre ^ " ")
$ k
$ fits_breaks_if cnd suf ~hint:(1000, 0) suf
let wrap_fits_breaks ?(space = true) conf x =
wrap_fits_breaks_if ~space conf true x
(** Boxes ---------------------------------------------------------------*)
let apply_max_indent n = Option.value_map !max_indent ~f:(min n) ~default:n
let open_box ?name n =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
let n = apply_max_indent n in
Box_debug.box_open ~stack ?name "b" n fs ;
Format_.pp_open_box fs n )
and open_vbox ?name n =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
let n = apply_max_indent n in
Box_debug.box_open ~stack ?name "v" n fs ;
Format_.pp_open_vbox fs n )
and open_hvbox ?name n =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
let n = apply_max_indent n in
Box_debug.box_open ~stack ?name "hv" n fs ;
Format_.pp_open_hvbox fs n )
and open_hovbox ?name n =
let stack = Box_debug.get_stack () in
with_pp (fun fs ->
let n = apply_max_indent n in
Box_debug.box_open ~stack ?name "hov" n fs ;
Format_.pp_open_hovbox fs n )
and close_box =
with_pp (fun fs ->
Box_debug.box_close fs ;
Format_.pp_close_box fs () )
(** Wrapping boxes ------------------------------------------------------*)
let cbox ?name n = wrap (open_box ?name n) close_box
and vbox ?name n = wrap (open_vbox ?name n) close_box
and hvbox ?name n = wrap (open_hvbox ?name n) close_box
and hovbox ?name n = wrap (open_hovbox ?name n) close_box
and cbox_if ?name cnd n = wrap_if cnd (open_box ?name n) close_box
and vbox_if ?name cnd n = wrap_if cnd (open_vbox ?name n) close_box
and hvbox_if ?name cnd n = wrap_if cnd (open_hvbox ?name n) close_box
and hovbox_if ?name cnd n = wrap_if cnd (open_hovbox ?name n) close_box
|