package serde_derive

  1. Overview
  2. Docs

Source file ser.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
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
open Ppxlib
module Ast = Ast_builder.Default

(** helpers *)
let loc ~ctxt = Expansion_context.Deriver.derived_item_loc ctxt

let var ~ctxt name =
  let loc = loc ~ctxt in
  Loc.make ~loc name

(** implementation *)

let rec ser_fun ~ctxt ~v (t : core_type) =
  let loc = loc ~ctxt in
  match t.ptyp_desc with
  (* Serialize a constructor *)
  | Ptyp_constr (name, _) -> (
      match name.txt |> Longident.name with
      | "bool" -> [%expr Ser.serialize_bool [%e v]]
      | "char" -> [%expr Ser.serialize_char [%e v]]
      | "float" -> [%expr Ser.serialize_float [%e v]]
      | "int" -> [%expr Ser.serialize_int [%e v]]
      | "string" -> [%expr Ser.serialize_string [%e v]]
      | "unit" -> [%expr Ser.serialize_unit [%e v]]
      | _ ->
          let ser_fn_name =
            match name.txt |> Longident.flatten_exn |> List.rev with
            | name :: [] -> "serialize_" ^ name
            | name :: path ->
                ("serialize_" ^ name) :: path |> List.rev |> String.concat "."
            | _ -> "unknown"
          in

          let fn =
            ser_fn_name |> Longident.parse |> var ~ctxt |> Ast.pexp_ident ~loc
          in

          Ast.pexp_apply ~loc fn [ (Nolabel, v) ])
  (* Destructure a tuple and deserialize all of its fields *)
  | Ptyp_tuple parts ->
      let pats =
        List.mapi
          (fun i part ->
            let f_idx = "f_" ^ Int.to_string i in
            let pat = f_idx |> var ~ctxt |> Ast.ppat_var ~loc in
            let exp =
              f_idx |> Longident.parse |> var ~ctxt |> Ast.pexp_ident ~loc
            in
            (pat, (exp, part)))
          parts
      in

      let destruct, _exprs = List.split pats in
      let vb =
        let destruct = destruct |> Ast.ppat_tuple ~loc in
        [ Ast.value_binding ~loc ~pat:destruct ~expr:v ]
      in

      let keys, exprs =
        parts
        |> List.mapi (fun idx ctyp ->
               let f_idx = "f_" ^ Int.to_string idx in
               let pat = f_idx |> var ~ctxt |> Ast.ppat_var ~loc in
               let var =
                 f_idx |> Longident.parse |> var ~ctxt |> Ast.pexp_ident ~loc
               in
               let fn = ser_fun ~ctxt ctyp ~v:var in
               (var, (pat, fn)))
        |> List.split
      in

      let ser_call =
        [%expr
          Ser.serialize_tuple
            ~size:[%e List.length parts |> Ast.eint ~loc]
            ~elements]
      in

      let field_list =
        Ast.pexp_let ~loc Nonrecursive
          [
            Ast.value_binding ~loc
              ~pat:[%pat? elements]
              ~expr:(Ast.elist ~loc keys);
          ]
          ser_call
      in

      Ast.pexp_let ~loc Nonrecursive vb
        (List.fold_left
           (fun body (pat, exp) ->
             let op = var ~ctxt "let*" in
             let let_ = Ast.binding_op ~op ~loc ~pat ~exp in
             Ast.letop ~let_ ~ands:[] ~body |> Ast.pexp_letop ~loc)
           field_list (List.rev exprs))
  (* Unsupported serialization for these *)
  | Ptyp_any | Ptyp_var _
  | Ptyp_object (_, _)
  | Ptyp_class (_, _)
  | Ptyp_alias (_, _)
  | Ptyp_variant (_, _, _)
  | Ptyp_poly (_, _)
  | Ptyp_package _ | Ptyp_extension _
  | Ptyp_arrow (_, _, _) ->
      [%expr ()]

let gen_record_field_impl ~ctxt i ldecl =
  let loc = loc ~ctxt in
  let f_idx = "f_" ^ Int.to_string i in
  let pat = f_idx |> var ~ctxt |> Ast.ppat_var ~loc in
  let var = f_idx |> Longident.parse |> var ~ctxt |> Ast.pexp_ident ~loc in
  let fn = ser_fun ~ctxt ldecl.pld_type ~v:var in
  let kv = [%expr [%e ldecl.pld_name.txt |> Ast.estring ~loc], [%e var]] in
  (kv, (pat, fn))

let gen_serialize_record_variant_impl ~ctxt ~typename ~variant_name ~idx fields
    =
  let loc = loc ~ctxt in

  let keys, exprs =
    fields |> List.mapi (gen_record_field_impl ~ctxt) |> List.split
  in

  let ser_call =
    [%expr
      Ser.serialize_record_variant
        ~typename:[%e typename.txt |> Ast.estring ~loc]
        ~variant_idx:[%e idx + 1 |> Ast.eint ~loc]
        ~variant_name:[%e variant_name.txt |> Ast.estring ~loc]
        ~variant_size:[%e List.length keys |> Ast.eint ~loc]
        ~fields]
  in

  let field_list =
    Ast.pexp_let ~loc Nonrecursive
      [ Ast.value_binding ~loc ~pat:[%pat? fields] ~expr:(Ast.elist ~loc keys) ]
      ser_call
  in

  List.fold_left
    (fun body (pat, exp) ->
      let op = var ~ctxt "let*" in
      let let_ = Ast.binding_op ~op ~loc ~pat ~exp in
      Ast.letop ~let_ ~ands:[] ~body |> Ast.pexp_letop ~loc)
    field_list (List.rev exprs)

let gen_tuple_field_impl ~ctxt i ctyp =
  let loc = loc ~ctxt in
  let f_idx = "f_" ^ Int.to_string i in
  let pat = f_idx |> var ~ctxt |> Ast.ppat_var ~loc in
  let var = f_idx |> Longident.parse |> var ~ctxt |> Ast.pexp_ident ~loc in
  let fn = ser_fun ~ctxt ctyp ~v:var in
  (var, (pat, fn))

let gen_serialize_tuple_variant_impl ~ctxt ~typename ~variant_name ~idx parts =
  let loc = loc ~ctxt in

  let keys, exprs =
    parts |> List.mapi (gen_tuple_field_impl ~ctxt) |> List.split
  in

  let ser_call =
    [%expr
      Ser.serialize_tuple_variant
        ~typename:[%e typename.txt |> Ast.estring ~loc]
        ~variant_idx:[%e idx + 1 |> Ast.eint ~loc]
        ~variant_name:[%e variant_name.txt |> Ast.estring ~loc]
        ~variant_size:[%e List.length keys |> Ast.eint ~loc]
        ~fields]
  in

  let field_list =
    Ast.pexp_let ~loc Nonrecursive
      [ Ast.value_binding ~loc ~pat:[%pat? fields] ~expr:(Ast.elist ~loc keys) ]
      ser_call
  in

  List.fold_left
    (fun body (pat, exp) ->
      let op = var ~ctxt "let*" in
      let let_ = Ast.binding_op ~op ~loc ~pat ~exp in
      Ast.letop ~let_ ~ands:[] ~body |> Ast.pexp_letop ~loc)
    field_list (List.rev exprs)

let gen_serialize_unit_variant_impl ~ctxt ~typename ~variant_name ~idx =
  let loc = loc ~ctxt in

  [%expr
    Ser.serialize_unit_variant
      ~typename:[%e typename.txt |> Ast.estring ~loc]
      ~variant_idx:[%e idx + 1 |> Ast.eint ~loc]
      ~variant_name:[%e variant_name.txt |> Ast.estring ~loc]]

let gen_serialize_variant_ctr_impl ~ctxt ~typename idx ctr =
  let variant_name = ctr.pcd_name in
  match ctr.pcd_args with
  | Pcstr_tuple [] ->
      gen_serialize_unit_variant_impl ~ctxt ~typename ~variant_name ~idx
  | Pcstr_tuple parts ->
      gen_serialize_tuple_variant_impl ~ctxt ~typename ~variant_name ~idx parts
  | Pcstr_record fields ->
      gen_serialize_record_variant_impl ~ctxt ~typename ~variant_name ~idx
        fields

let gen_serialize_variant_impl ~ctxt typename constructors =
  let loc = Expansion_context.Deriver.derived_item_loc ctxt in
  let cases =
    List.mapi
      (fun idx ctr ->
        let args =
          match ctr.pcd_args with
          | Pcstr_tuple [] -> None
          | Pcstr_tuple fields ->
              let fields =
                List.mapi
                  (fun i _ ->
                    let f_idx = "f_" ^ Int.to_string i in
                    let pat = f_idx |> var ~ctxt |> Ast.ppat_var ~loc in
                    pat)
                  fields
              in
              Some (Ast.ppat_tuple ~loc fields)
          | Pcstr_record fields ->
              let fields =
                List.mapi
                  (fun i field ->
                    let f_idx = "f_" ^ Int.to_string i in
                    let pat = f_idx |> var ~ctxt |> Ast.ppat_var ~loc in
                    (field.pld_name.txt |> Longident.parse |> Loc.make ~loc, pat))
                  fields
              in
              Some (Ast.ppat_record ~loc fields Closed)
        in

        let lhs =
          Ast.ppat_construct ~loc
            (ctr.pcd_name.txt |> Longident.parse |> Loc.make ~loc)
            args
        in
        let rhs = gen_serialize_variant_ctr_impl ~ctxt ~typename idx ctr in
        Ast.case ~lhs ~rhs ~guard:None)
      constructors
  in
  Ast.pexp_match ~loc [%expr t] cases

let gen_serialize_record_impl ~ctxt typename fields =
  let loc = Expansion_context.Deriver.derived_item_loc ctxt in

  let extract_fields =
    let fields =
      List.mapi
        (fun i field ->
          let f_idx = "f_" ^ Int.to_string i in
          let pat = f_idx |> var ~ctxt |> Ast.ppat_var ~loc in
          (field.pld_name.txt |> Longident.parse |> Loc.make ~loc, pat))
        fields
    in
    Ast.ppat_record ~loc fields Closed
  in

  let keys, exprs =
    fields |> List.mapi (gen_record_field_impl ~ctxt) |> List.split
  in

  let ser_call =
    [%expr
      Ser.serialize_record
        ~typename:[%e typename.txt |> Ast.estring ~loc]
        ~size:[%e List.length keys |> Ast.eint ~loc]
        ~fields]
  in

  let field_list =
    Ast.pexp_let ~loc Nonrecursive
      [ Ast.value_binding ~loc ~pat:[%pat? fields] ~expr:(Ast.elist ~loc keys) ]
      ser_call
  in

  let body =
    List.fold_left
      (fun body (pat, exp) ->
        let op = var ~ctxt "let*" in
        let let_ = Ast.binding_op ~op ~loc ~pat ~exp in
        Ast.letop ~let_ ~ands:[] ~body |> Ast.pexp_letop ~loc)
      field_list (List.rev exprs)
  in

  let t = "t" |> Longident.parse |> var ~ctxt |> Ast.pexp_ident ~loc in
  let vb = [ Ast.value_binding ~loc ~pat:extract_fields ~expr:t ] in
  Ast.pexp_let ~loc Nonrecursive vb body

let gen_serialize_abstract_impl ~ctxt _typename core_type =
  let loc = loc ~ctxt in
  let v = "t" |> Longident.parse |> var ~ctxt |> Ast.pexp_ident ~loc in
  ser_fun ~ctxt ~v core_type

let gen_serialize_impl ~ctxt type_decl =
  let loc = loc ~ctxt in

  let typename = type_decl.ptype_name.txt in

  let body =
    match type_decl with
    | { ptype_kind = Ptype_variant constructors; ptype_name; _ } ->
        gen_serialize_variant_impl ~ctxt ptype_name constructors
    | { ptype_kind = Ptype_record label_declarations; ptype_name; _ } ->
        gen_serialize_record_impl ~ctxt ptype_name label_declarations
    | {
     ptype_kind = Ptype_abstract;
     ptype_name;
     ptype_manifest = Some manifest;
     _;
    } ->
        gen_serialize_abstract_impl ~ctxt ptype_name manifest
    | { ptype_kind; ptype_name; _ } ->
        let err =
          match ptype_kind with
          | Ptype_abstract -> "unsupported abstract type"
          | Ptype_variant _ -> "unsupported variant type"
          | Ptype_record _ -> "unsupported record type"
          | Ptype_open -> "unsupported open type"
        in
        [%expr
          [%e ptype_name.txt |> Ast.estring ~loc] [%e err |> Ast.estring ~loc]]
  in
  let serializer_name =
    "serialize_" ^ typename |> var ~ctxt |> Ast.ppat_var ~loc
  in
  [%stri
    (** Serialize a value of this type into Serde.data *)
    let [%p serializer_name] =
     fun t ->
      let ( let* ) = Result.bind in
      (* NOTE(@ostera): horrible hack to avoid the unused warnings *)
      let* () = Ok () in
      [%e body]]

let generate_impl ~ctxt (_rec_flag, type_declarations) =
  List.map (gen_serialize_impl ~ctxt) type_declarations

let impl_generator = Deriving.Generator.V2.make_noarg generate_impl

(** interface *)

let generate_intf ~ctxt:_ (_rec_flag, _type_declarations) = []
let intf_generator = Deriving.Generator.V2.make_noarg generate_intf

(** registration *)

let register =
  Deriving.add "serializer" ~str_type_decl:impl_generator
    ~sig_type_decl:intf_generator