package diff

  1. Overview
  2. Docs
A library for generating diffs from product types

Install

Dune Dependency

Authors

Maintainers

Sources

1.0.2.tar.gz
md5=6316048a1c65fa28a0ee71fa1450d87c
sha512=07d1be5825e00f6d10e6c9d175ef7cc300dc6b8993d91004817d4dc086298798fe7d017b8024d1c1b62d35fbf091077c1fb518418c7f1039e3e071ef07db0f5a

doc/src/ppx_diff/ppx_diff.ml.html

Source file ppx_diff.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
open Ppxlib
open Ast_builder.Default

module Utils = struct
  let constructor_name_of_field ~field_name ~name =
    match name with
    | "t" -> String.capitalize_ascii field_name
    | _ -> String.capitalize_ascii name ^ "_" ^ field_name

  let getter_name ~name =
    match name with "t" -> "getter" | _ -> name ^ "_getter"

  let setter_name ~name =
    match name with "t" -> "setter" | _ -> name ^ "_setter"
end

module Lidents = struct
  let field ~loc = { loc; txt = Longident.parse "Diff.Field.t" }
  let register ~loc = { loc; txt = Longident.parse "Diff.Field.register" }
  let set_name ~loc = { loc; txt = Longident.parse "Diff.Field.set_name" }
  let unit ~loc = { loc; txt = Lident "()" }
  let option ~loc = { loc; txt = Lident "option" }
  let some ~loc = { loc; txt = Lident "Some" }
  let none ~loc = { loc; txt = Lident "None" }
  let getter ~loc = { loc; txt = Longident.parse "Diff.Field.getter" }
  let setter ~loc = { loc; txt = Longident.parse "Diff.Field.setter" }

  let constructor_name ~loc ~name ~field_name =
    { txt = Lident (Utils.constructor_name_of_field ~field_name ~name); loc }

  let getter_name ~loc ~name = { txt = Lident (Utils.getter_name ~name); loc }
  let setter_name ~loc ~name = { txt = Lident (Utils.setter_name ~name); loc }
end

module Impl = struct
  let generate_pre_attr ~loc =
    pstr_attribute ~loc
      (attribute ~loc
         ~name:{ txt = "ocaml.warning"; loc }
         ~payload:
           (PStr
              [
                pstr_eval ~loc
                  (pexp_constant ~loc (Pconst_string ("-23-39", loc, None)))
                  [];
              ]))

  let generate_post_attr ~loc =
    pstr_attribute ~loc
      (attribute ~loc
         ~name:{ txt = "ocaml.warning"; loc }
         ~payload:
           (PStr
              [
                pstr_eval ~loc
                  (pexp_constant ~loc (Pconst_string ("+23+39", loc, None)))
                  [];
              ]))

  let generate_field
      ~field:
        { pld_name = { txt = field_name; _ }; pld_loc = loc; pld_type = typ; _ }
      ~name ~ct =
    let txt = Utils.constructor_name_of_field ~field_name ~name in
    extension_constructor ~loc ~name:{ txt; loc }
      ~kind:
        (Pext_decl
           ( [],
             Pcstr_tuple [],
             Some (ptyp_constr ~loc (Lidents.field ~loc) [ ct; typ ]) ))

  let generate_fields ~fields ~name ~loc ~ct =
    pstr_typext ~loc
      (type_extension ~loc ~path:(Lidents.field ~loc)
         ~params:
           (List.init 2 (fun _ -> (ptyp_any ~loc, (NoVariance, NoInjectivity))))
         ~constructors:
           (List.map (fun field -> generate_field ~name ~field ~ct) fields)
         ~private_:Public)

  let generate_getter_case
      ~field:{ pld_name = { txt = field_name; _ }; pld_loc = loc; _ } ~name =
    case
      ~lhs:
        (ppat_construct ~loc
           (Lidents.constructor_name ~loc ~name ~field_name)
           None)
      ~guard:None
      ~rhs:
        (pexp_construct ~loc (Lidents.some ~loc)
           (Some
              (pexp_field ~loc
                 (pexp_ident ~loc { txt = Lident name; loc })
                 { txt = Lident field_name; loc })))

  let generate_getter_cases ~fields ~loc ~name =
    List.rev
      (case ~lhs:(ppat_any ~loc) ~guard:None
         ~rhs:(pexp_construct ~loc (Lidents.none ~loc) None)
      :: List.rev_map (fun field -> generate_getter_case ~field ~name) fields)

  let generate_getter ~fields ~name ~loc =
    value_binding ~loc
      ~pat:
        (ppat_constraint ~loc
           (ppat_var ~loc { txt = Utils.getter_name ~name; loc })
           (ptyp_constr ~loc (Lidents.getter ~loc) []))
      ~expr:
        (pexp_record ~loc
           [
             ( { txt = Lident "f"; loc },
               pexp_newtype ~loc { txt = "a"; loc }
                 (pexp_newtype ~loc { txt = "b"; loc }
                    (pexp_fun ~loc Nolabel None
                       (ppat_constraint ~loc
                          (ppat_var ~loc { txt = name; loc })
                          (ptyp_constr ~loc { txt = Lident "a"; loc } []))
                       (pexp_fun ~loc Nolabel None
                          (ppat_constraint ~loc
                             (ppat_var ~loc { txt = "__field"; loc })
                             (ptyp_constr ~loc (Lidents.field ~loc)
                                [
                                  ptyp_constr ~loc { txt = Lident "a"; loc } [];
                                  ptyp_constr ~loc { txt = Lident "b"; loc } [];
                                ]))
                          (pexp_constraint ~loc
                             (pexp_match ~loc
                                (pexp_ident ~loc
                                   { txt = Lident "__field"; loc })
                                (generate_getter_cases ~loc ~fields ~name))
                             (ptyp_constr ~loc (Lidents.option ~loc)
                                [
                                  ptyp_constr ~loc { txt = Lident "b"; loc } [];
                                ]))))) );
           ]
           None)

  let generate_setter_case
      ~field:{ pld_name = { txt = field_name; _ }; pld_loc = loc; _ } ~name =
    case
      ~lhs:
        (ppat_construct ~loc
           {
             txt = Lident (Utils.constructor_name_of_field ~field_name ~name);
             loc;
           }
           None)
      ~guard:None
      ~rhs:
        (pexp_construct ~loc (Lidents.some ~loc)
           (Some
              (pexp_record ~loc
                 [
                   ( { txt = Lident field_name; loc },
                     pexp_ident ~loc { txt = Lident "__v"; loc } );
                 ]
                 (Some (pexp_ident ~loc { txt = Lident name; loc })))))

  let generate_setter_cases ~fields ~loc ~name =
    List.rev
      (case ~lhs:(ppat_any ~loc) ~guard:None
         ~rhs:(pexp_construct ~loc (Lidents.none ~loc) None)
      :: List.rev_map (fun field -> generate_setter_case ~field ~name) fields)

  let generate_setter ~fields ~name ~loc =
    value_binding ~loc
      ~pat:
        (ppat_constraint ~loc
           (ppat_var ~loc { txt = Utils.setter_name ~name; loc })
           (ptyp_constr ~loc (Lidents.setter ~loc) []))
      ~expr:
        (pexp_record ~loc
           [
             ( { txt = Lident "f"; loc },
               pexp_newtype ~loc { txt = "a"; loc }
                 (pexp_newtype ~loc { txt = "b"; loc }
                    (pexp_fun ~loc Nolabel None
                       (ppat_constraint ~loc
                          (ppat_var ~loc { txt = name; loc })
                          (ptyp_constr ~loc { txt = Lident "a"; loc } []))
                       (pexp_fun ~loc Nolabel None
                          (ppat_constraint ~loc
                             (ppat_var ~loc { txt = "__field"; loc })
                             (ptyp_constr ~loc (Lidents.field ~loc)
                                [
                                  ptyp_constr ~loc { txt = Lident "a"; loc } [];
                                  ptyp_constr ~loc { txt = Lident "b"; loc } [];
                                ]))
                          (pexp_fun ~loc Nolabel None
                             (ppat_constraint ~loc
                                (ppat_var ~loc { txt = "__v"; loc })
                                (ptyp_constr ~loc { txt = Lident "b"; loc } []))
                             (pexp_constraint ~loc
                                (pexp_match ~loc
                                   (pexp_ident ~loc
                                      { txt = Lident "__field"; loc })
                                   (generate_setter_cases ~loc ~fields ~name))
                                (ptyp_constr ~loc (Lidents.option ~loc)
                                   [
                                     ptyp_constr ~loc { txt = Lident "a"; loc }
                                       [];
                                   ])))))) );
           ]
           None)

  let generate_register ~fields ~name ~loc =
    pstr_value ~loc Nonrecursive
      (List.map
         (fun { pld_name = { txt = field_name; _ }; pld_loc = loc; _ } ->
           let constructor_name =
             Utils.constructor_name_of_field ~field_name ~name
           in
           value_binding ~loc
             ~pat:
               (ppat_constraint ~loc (ppat_any ~loc)
                  (ptyp_constr ~loc { txt = Lident "unit"; loc } []))
             ~expr:
               (pexp_apply ~loc
                  (pexp_ident ~loc (Lidents.register ~loc))
                  [
                    ( Labelled "name",
                      pexp_constant ~loc
                        (Pconst_string (constructor_name, loc, None)) );
                    ( Nolabel,
                      pexp_construct ~loc
                        { txt = Lident constructor_name; loc }
                        None );
                    (Nolabel, pexp_ident ~loc (Lidents.getter_name ~loc ~name));
                    (Nolabel, pexp_ident ~loc (Lidents.setter_name ~loc ~name));
                  ]))
         fields)

  let generate_getter_and_setter ~fields ~name ~loc =
    pstr_value ~loc Nonrecursive
      [ generate_getter ~fields ~name ~loc; generate_setter ~fields ~name ~loc ]

  let generate ~ctxt:_ (_rec_flag, type_declarations) =
    List.fold_left
      (fun acc -> function
        | {
            ptype_kind = Ptype_abstract | Ptype_variant _ | Ptype_open;
            ptype_loc = loc;
            _;
          } ->
            let ext =
              Location.error_extensionf ~loc
                "Cannot derive diffs for non-record types"
            in
            pstr_extension ~loc ext [] :: acc
        | {
            ptype_kind = Ptype_record fields;
            ptype_loc = loc;
            ptype_name = { txt = name; _ };
            _;
          } as td ->
            let ct = core_type_of_type_declaration td in
            generate_pre_attr ~loc
            :: generate_fields ~fields ~loc ~name ~ct
            :: generate_getter_and_setter ~fields ~name ~loc
            :: generate_register ~fields ~name ~loc
            :: generate_post_attr ~loc :: acc)
      [] type_declarations
end

let str_type_decl = Deriving.Generator.V2.make_noarg Impl.generate
let deriver = Deriving.add "diff" ~str_type_decl
OCaml

Innovation. Community. Security.