Source file info_intf.ml

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
(** [Info] is a library for lazily constructing human-readable information as a string or
    sexp, with a primary use being error messages.

    Using [Info] is often preferable to [sprintf] or manually constructing strings because
    you don't have to eagerly construct the string -- you only need to pay when you
    actually want to display the info, which for many applications is rare. Using [Info]
    is also better than creating custom exceptions because you have more control over the
    format.

    Info is intended to be constructed in the following style; for simple info, you write:

    {[
      Info.of_string "Unable to find file"
    ]}

    Or for a more descriptive [Info] without attaching any content (but evaluating the
    result eagerly):

    {[
      Info.createf "Process %s exited with code %d" process exit_code
    ]}

    For info where you want to attach some content, you would write:

    {[
      Info.create "Unable to find file" filename [%sexp_of: string]
    ]}

    Or even,

    {[
      Info.create
        "price too big"
        (price, [ `Max max_price ])
        [%sexp_of: float * [ `Max of float ]]
    ]}

    Note that an [Info.t] can be created from any arbitrary sexp with [Info.t_of_sexp]. *)

open! Import
module Invariant = Invariant_intf.Definitions
module Sexp = Sexp0

module Definitions = struct
  module type Internal_repr = sig
    type info

    (** The internal representation. It is exposed so that we can write efficient
        serializers outside of this module. *)
    type t =
      | Could_not_construct of Sexp.t
      | String of string
      | Exn of exn Modes.Global.t
      | Sexp of Sexp.t
      | Tag_sexp of string * Sexp.t * Source_code_position0.t option
      | Tag_t of string * t
      | Tag_arg of string * Sexp.t * t
      | Of_list of int option * t list
      | With_backtrace of t * string (** The second argument is the backtrace *)
    [@@deriving sexp_of ~stackify]

    val of_info : local_ info -> t

    val%template to_info : t @ p -> info @ p [@@mode p = (portable, nonportable)]
  end

  (** [Info.t Modes.Portable.t] with some common ppxes derived on it. It also re-exports
      some functions of [Info] so users don't need to frequently wrap and unwrap
      [Modes.Portable.t] just to manipulate the underlying info. *)
  module type Portable = sig
    type info

    type t = info Modes.Portable.t
    [@@deriving
      compare ~localize, equal ~localize, globalize, hash, sexp ~stackify, sexp_grammar]

    val create_s : Sexp.t -> t
    val of_string : string -> t
    val of_list : t list -> t
    val of_portable_lazy : string Portable_lazy.t -> t
    val of_portable_lazy_sexp : Sexp.t Portable_lazy.t -> t
    val of_portable_lazy_t : t Portable_lazy.t -> t

    val%template create
      : ('a : k mod contended).
      ?here:Source_code_position0.t
      -> ?strict:unit
      -> string
      -> 'a @ portable
      -> ('a -> Sexp.t) @ portable
      -> t
    [@@kind k = (bits64, float64, value_or_null)]

    val tag_s : t -> tag:Sexp.t -> t
    val tag : t -> tag:string -> t

    val tag_arg
      : ('a : value_or_null mod contended).
      t -> string -> 'a @ portable -> ('a -> Sexp.t) @ portable -> t

    val of_thunk : (unit -> string) @ portable -> t
    val createf : ('a, unit, string, t) format4 -> 'a
  end

  module type S0 = sig @@ portable
    (** Serialization and comparison force the lazy message. *)
    type t : value mod contended global
    [@@deriving
      compare ~localize, equal ~localize, globalize, hash, sexp ~stackify, sexp_grammar]

    type info := t

    (** Explicitly indicate that [t_of_sexp] produces a portable [t]. This is nicer for
        the user: you can do more things with a portable [t], e.g. move it between
        domains. *)
    val t_of_sexp : Sexp.t -> t @ portable

    include Invariant.S with type t := t

    (** [to_string_hum] forces the lazy message, which might be an expensive operation.

        [to_string_hum] usually produces a sexp; however, it is guaranteed that
        [to_string_hum (of_string s) = s].

        If this string is going to go into a log file, you may find it useful to ensure
        that the string is only one line long. To do this, use [to_string_mach t]. *)
    val to_string_hum : t -> string

    (** [to_string_mach t] outputs [t] as a sexp on a single line. *)
    val to_string_mach : t -> string

    val of_string : string -> t @ portable

    (** Be careful that the body of the lazy or thunk does not access mutable data, since
        it will only be called at an undetermined later point. *)

    val of_lazy : string Lazy.t -> t
    val of_lazy_sexp : Sexp.t Lazy.t -> t
    val of_lazy_t : t Lazy.t -> t

    val%template of_thunk : (unit -> string) @ p -> t @ p
    [@@mode p = (portable, nonportable)]

    val of_portable_lazy : string Portable_lazy.t -> t @ portable
    val of_portable_lazy_sexp : Sexp.t Portable_lazy.t -> t @ portable
    val of_portable_lazy_t : t Portable_lazy.t -> t @ portable

    (** For [create message a sexp_of_a], [sexp_of_a a] is lazily computed, when the info
        is converted to a sexp. So if [a] is mutated in the time between the call to
        [create] and the sexp conversion, those mutations will be reflected in the sexp.
        Use [~strict:()] to force [sexp_of_a a] to be computed immediately. *)
    val%template create
      : ('a : k).
      ?here:Source_code_position0.t
      -> ?strict:unit
      -> string
      -> 'a @ p
      -> ('a @ c -> Sexp.t) @ p
      -> t @ p
    [@@kind k = (bits64, float64, value_or_null)]
    [@@mode (p, c) = ((nonportable, uncontended), (portable, contended))]

    val create_s : Sexp.t -> t @ portable

    (** Constructs a [t] containing only a string from a format. This eagerly constructs
        the string. *)
    val createf : ('a, unit, string, t) format4 -> 'a

    (** [createf_portable format arg1 arg2 ... ()] is like [createf format arg1 arg2 ...],
        except creating a portable error. *)
    val createf_portable : ('a, unit, string, unit -> t @ portable) format4 -> 'a

    (** Adds a string to the front. *)
    val%template tag : t @ p -> tag:string -> t @ p
    [@@mode p = (portable, nonportable)]

    (** Adds a sexp to the front. *)
    val%template tag_s : t @ p -> tag:Sexp.t -> t @ p
    [@@mode p = (portable, nonportable)]

    (** Adds a lazy string to the front. *)
    val tag_lazy : t -> tag:string Lazy.t -> t

    (** Adds a portable lazy string to the front. *)
    val%template tag_portable_lazy : t @ p -> tag:string Portable_lazy.t -> t @ p
    [@@mode p = (portable, nonportable)]

    (** Adds a lazy sexp to the front. *)
    val tag_s_lazy : t -> tag:Sexp.t Lazy.t -> t

    (** Adds a portable lazy sexp to the front. *)
    val%template tag_s_portable_lazy : t @ p -> tag:Sexp.t Portable_lazy.t -> t @ p
    [@@mode p = (portable, nonportable)]

    (** Adds a string and some other data in the form of an s-expression at the front. *)
    val%template tag_arg
      : ('a : value_or_null).
      t @ p -> string -> 'a @ p -> ('a @ c -> Sexp.t) @ p -> t @ p
    [@@mode (p, c) = ((nonportable, uncontended), (portable, contended))]

    (** Combines multiple infos into one. *)
    val%template of_list : t list @ p -> t @ p
    [@@mode p = (portable, nonportable)]

    (** [of_exn] and [to_exn] are primarily used with [Error], but their definitions have
        to be here because they refer to the underlying representation.

        [~backtrace:`Get] attaches the backtrace for the most recent exception. The same
        caveats as for [Printexc.print_backtrace] apply. [~backtrace:(`This s)] attaches
        the backtrace [s]. The default is no backtrace. *)
    val of_exn : ?backtrace:[ `Get | `This of string ] -> exn -> t @ portable

    val to_exn : t -> exn
    val pp : Formatter.t -> t -> unit

    module Internal_repr : Internal_repr with type info := t
    module Portable : Portable with type info := info

    val of_portable : Portable.t -> t @ portable
    val to_portable : t @ portable -> Portable.t

    (** Constructs a portable info out of a possibly non-portable info. This operation is
        less expensive than [err |> sexp_of_t |> create_s], but it's not a no-op.

        It's not a no-op because it needs to force any non-portable computation of the
        input info. *)
    val portabilize : t -> t @ portable

    (** Convert to [Portable.t] via [portabilize]. *)
    val to_portable_portabilize : t -> Portable.t
  end

  module type S = sig @@ portable
    include S0

    (** Unlike the standard [to_string_*] functions under [S], these encode any [t] that's
        represented by a sexp [1] using [Sexp.Utf8.to_string_*], ensuring that valid UTF-8
        sexp atoms get printed as-is. Standard [to_string_*] functions, by contrast,
        encode sexps using [Sexp.to_string_*], encoding UTF-8 / other non-ASCII characters
        using escape sequences.

        [1] this will be true in most cases except when created out of [of_string], which
            simply gets stored as a string, not a sexp. *)
    module Utf8 : sig
      (** [to_string_hum] forces the lazy message, which might be an expensive operation.

          [to_string_hum] usually produces a sexp; however, it is guaranteed that
          [to_string_hum (of_string s) = s].

          If this string is going to go into a log file, you may find it useful to ensure
          that the string is only one line long. To do this, use [to_string_mach t]. *)
      val to_string_hum : t -> string

      (** [to_string_mach t] outputs [t] as a sexp on a single line. *)
      val to_string_mach : t -> string
    end
  end
end

module type Info0 = sig @@ portable
  include module type of struct
    include Definitions
  end

  include S0 (** @inline *)
end

module type Info = sig @@ portable
  include Info0 (** @inline *)

  include S with type t := t (** @inline *)
end