File: Fmt.ml

package info (click to toggle)
ocamlformat 0.27.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 12,068 kB
  • sloc: ml: 61,288; pascal: 4,739; lisp: 229; sh: 217; makefile: 121
file content (308 lines) | stat: -rw-r--r-- 9,451 bytes parent folder | download
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