package serde_derive

  1. Overview
  2. Docs
Derive-macros for the Serde serialization framework

Install

dune-project
 Dependency

Authors

Maintainers

Sources

serde-0.0.2.tbz
sha256=0766b13f1e9421f98fab8efc5d43657229f3b3bc13ca4722086da634fe076ca4
sha512=1023dcd53c4b69032f466b338e2f0146ee3443b2ec4ce8db2d1f226e1409d81a3da3767ac73e9e047fd61b904a829ff841c26317c8ea050351ba6f70ab4158c4

doc/src/serde_derive/ser.ml.html

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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
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

let gensym () =
  let counter = ref 0 in
  fun ~ctxt ->
    counter := !counter + 1;
    var ~ctxt ("v_" ^ Int.to_string !counter)

let serializer_fn_name_for_longident name =
  let name =
    match name.txt |> Longident.flatten_exn |> List.rev with
    | name :: [] -> "serialize_" ^ name
    | name :: path ->
        ("serialize_" ^ name) :: path |> List.rev |> String.concat "."
    | _ -> "unknown"
  in
  Longident.parse name

let is_primitive = function
  | "bool" | "char" | "float" | "int" | "int32" | "int64" | "string" | "list"
  | "array" | "unit" | "option" ->
      true
  | _ -> false

let rec serializer_for_type ~ctxt (core_type : Parsetree.core_type) =
  let loc = loc ~ctxt in
  match core_type.ptyp_desc with
  | Ptyp_constr (name, arg :: []) when is_primitive (Longident.name name.txt) ->
      let type_ser = serializer_for_type ~ctxt arg in
      let name = Ast.pexp_ident ~loc name in
      [%expr s ([%e name] [%e type_ser])]
  | Ptyp_constr (name, []) when is_primitive (Longident.name name.txt) ->
      Ast.pexp_ident ~loc name
  | Ptyp_constr (name, _args) ->
      let ser_fn =
        serializer_fn_name_for_longident name
        |> var ~ctxt |> Ast.pexp_ident ~loc
      in
      [%expr s [%e ser_fn]]
  | Ptyp_any | Ptyp_var _
  | Ptyp_arrow (_, _, _)
  | Ptyp_tuple _
  | Ptyp_object (_, _)
  | Ptyp_class (_, _)
  | Ptyp_alias (_, _)
  | Ptyp_variant (_, _, _)
  | Ptyp_poly (_, _)
  | Ptyp_package _ | Ptyp_extension _ ->
      failwith "unsupported"

(** implementation *)

let gen_serialize_externally_tagged_variant_impl ~ctxt ptype_name
    type_attributes cstr_declarations =
  let loc = loc ~ctxt in
  let type_name = Ast.estring ~loc ptype_name.txt in

  let pattern_of_constructor cstr =
    match cstr.pcd_args with
    | Pcstr_tuple [] -> None
    | Pcstr_tuple parts ->
        let gensym = gensym () in
        Some
          (Ast.ppat_tuple ~loc
             (List.map (fun _ -> Ast.pvar ~loc (gensym ~ctxt).txt) parts))
    | Pcstr_record _ -> Some (Ast.pvar ~loc "r")
  in

  let ser_by_constructor type_name idx cstr =
    let idx = Ast.eint ~loc idx in
    let name = Ast.estring ~loc cstr.pcd_name.txt in
    match cstr.pcd_args with
    | Pcstr_tuple [] ->
        [%expr unit_variant ctx [%e type_name] [%e idx] [%e name]]
    | Pcstr_tuple [ arg ] ->
        let ser_fn = serializer_for_type ~ctxt arg in
        let arg_var = Ast.evar ~loc (gensym () ~ctxt).txt in
        let ser = [%expr [%e ser_fn] [%e arg_var]] in
        [%expr newtype_variant ctx [%e type_name] [%e idx] [%e name] [%e ser]]
    | Pcstr_tuple args ->
        let arg_count = Ast.eint ~loc (List.length args) in
        let gensym = gensym () in
        let calls =
          List.mapi
            (fun _idx arg ->
              let ser_fn = serializer_for_type ~ctxt arg in
              let arg_var = Ast.evar ~loc (gensym ~ctxt).txt in
              [%expr element ctx ([%e ser_fn] [%e arg_var])])
            args
        in

        let calls =
          List.fold_left
            (fun last expr ->
              [%expr
                let* () = [%e expr] in
                [%e last]])
            [%expr Ok ()] (List.rev calls)
        in
        [%expr
          tuple_variant ctx [%e type_name] [%e idx] [%e name] [%e arg_count]
            (fun ctx -> [%e calls])]
    | Pcstr_record labels ->
        let field_count = Ast.eint ~loc (List.length labels) in
        let labels = List.rev labels in
        let labels =
          List.map (Attributes.of_field_attributes type_attributes) labels
        in
        let fields =
          List.map
            (fun (field, attr) ->
              let field_name = Ast.estring ~loc Attributes.(attr.name) in
              let field_access =
                let field_name = Longident.parse field.pld_name.txt in
                Ast.pexp_field ~loc (Ast.evar ~loc "r")
                  (Loc.make ~loc field_name)
              in
              let serializer = serializer_for_type ~ctxt field.pld_type in
              [%expr
                field ctx [%e field_name] ([%e serializer] [%e field_access])])
            labels
        in
        let fields =
          List.fold_left
            (fun last curr ->
              [%expr
                let* () = [%e curr] in
                [%e last]])
            [%expr Ok ()] fields
        in

        [%expr
          record_variant ctx [%e type_name] [%e idx] [%e name] [%e field_count]
            (fun ctx -> [%e fields])]
  in

  let cases =
    List.mapi
      (fun idx (cstr : Parsetree.constructor_declaration) ->
        let lhs = Ast.pconstruct cstr (pattern_of_constructor cstr) in
        let rhs = ser_by_constructor type_name idx cstr in
        Ast.case ~lhs ~guard:None ~rhs)
      cstr_declarations
  in

  Ast.pexp_match ~loc [%expr t] cases

let gen_serialize_adjacently_tagged_variant_impl ~tag_field_name
    ~content_field_name ~ctxt ptype_name type_attributes cstr_declarations =
  let loc = loc ~ctxt in
  let type_name = Ast.estring ~loc ptype_name.txt in

  let pattern_of_constructor cstr =
    match cstr.pcd_args with
    | Pcstr_tuple [] -> None
    | Pcstr_tuple parts ->
        let gensym = gensym () in
        Some
          (Ast.ppat_tuple ~loc
             (List.map (fun _ -> Ast.pvar ~loc (gensym ~ctxt).txt) parts))
    | Pcstr_record _ -> Some (Ast.pvar ~loc "r")
  in

  let ser_by_constructor type_name cstr =
    let name = Ast.estring ~loc cstr.pcd_name.txt in
    match cstr.pcd_args with
    | Pcstr_tuple [] ->
        [%expr
          record ctx "" 1 (fun ctx ->
              field ctx [%e Ast.estring ~loc tag_field_name] (string [%e name]))]
    | Pcstr_tuple [ arg ] ->
        let ser_fn = serializer_for_type ~ctxt arg in
        let arg_var = Ast.evar ~loc (gensym () ~ctxt).txt in
        let ser = [%expr [%e ser_fn] [%e arg_var] ctx] in

        [%expr
          record ctx "" 2 (fun ctx ->
              let* () =
                field ctx
                  [%e Ast.estring ~loc tag_field_name]
                  (string [%e name])
              in
              field ctx [%e Ast.estring ~loc content_field_name] (fun ctx ->
                  [%e ser]))]
    | Pcstr_tuple args ->
        let arg_count = Ast.eint ~loc (List.length args) in
        let gensym = gensym () in
        let calls =
          List.mapi
            (fun _idx arg ->
              let ser_fn = serializer_for_type ~ctxt arg in
              let arg_var = Ast.evar ~loc (gensym ~ctxt).txt in
              [%expr element ctx ([%e ser_fn] [%e arg_var])])
            args
        in

        let calls =
          List.fold_left
            (fun last expr ->
              [%expr
                let* () = [%e expr] in
                [%e last]])
            [%expr Ok ()] (List.rev calls)
        in
        [%expr
          record ctx "" 2 (fun ctx ->
              let* () =
                field ctx
                  [%e Ast.estring ~loc tag_field_name]
                  (string [%e name])
              in
              field ctx [%e Ast.estring ~loc content_field_name] (fun ctx ->
                  sequence ctx [%e arg_count] (fun ctx -> [%e calls])))]
    | Pcstr_record labels ->
        let field_count = Ast.eint ~loc (List.length labels) in
        let labels = List.rev labels in
        let labels =
          List.map (Attributes.of_field_attributes type_attributes) labels
        in
        let fields =
          List.map
            (fun (field, attr) ->
              let field_name = Ast.estring ~loc Attributes.(attr.name) in
              let field_access =
                let field_name = Longident.parse field.pld_name.txt in
                Ast.pexp_field ~loc (Ast.evar ~loc "r")
                  (Loc.make ~loc field_name)
              in
              let serializer = serializer_for_type ~ctxt field.pld_type in
              [%expr
                field ctx [%e field_name] ([%e serializer] [%e field_access])])
            labels
        in
        let fields =
          List.fold_left
            (fun last curr ->
              [%expr
                let* () = [%e curr] in
                [%e last]])
            [%expr Ok ()] fields
        in
        [%expr
          record ctx "" 2 (fun ctx ->
              let* () =
                field ctx
                  [%e Ast.estring ~loc tag_field_name]
                  (string [%e name])
              in
              field ctx [%e Ast.estring ~loc content_field_name] (fun ctx ->
                  record ctx [%e type_name] [%e field_count] (fun ctx ->
                      [%e fields])))]
  in

  let cases =
    List.mapi
      (fun _idx (cstr : Parsetree.constructor_declaration) ->
        let lhs = Ast.pconstruct cstr (pattern_of_constructor cstr) in
        let rhs = ser_by_constructor type_name cstr in
        Ast.case ~lhs ~guard:None ~rhs)
      cstr_declarations
  in

  Ast.pexp_match ~loc [%expr t] cases

let gen_serialize_internally_tagged_variant_impl ~tag_field_name:_ ~ctxt:_
    _ptype_name _type_attributes _cstr_declarations =
  failwith "not implemented"

let gen_serialize_variant_impl ~ctxt ptype_name type_attributes
    cstr_declarations =
  match type_attributes.Attributes.variant_tagging_mode with
  | `externally_tagged ->
      gen_serialize_externally_tagged_variant_impl ~ctxt ptype_name
        type_attributes cstr_declarations
  | `internally_tagged tag_field_name ->
      gen_serialize_internally_tagged_variant_impl ~tag_field_name ~ctxt
        ptype_name type_attributes cstr_declarations
  | `adjacently_tagged (tag_field_name, content_field_name) ->
      gen_serialize_adjacently_tagged_variant_impl ~tag_field_name
        ~content_field_name ~ctxt ptype_name type_attributes cstr_declarations
  | _ -> failwith "not implemented"

let gen_serialize_record_impl ~ctxt ptype_name type_attributes
    label_declarations =
  let loc = loc ~ctxt in
  let type_name = Ast.estring ~loc ptype_name.txt in
  let field_count = Ast.eint ~loc (List.length label_declarations) in
  let labels = List.rev label_declarations in
  let labels =
    List.map (Attributes.of_field_attributes type_attributes) labels
  in

  let fields =
    List.map
      (fun (field, attr) ->
        let field_name = Ast.estring ~loc Attributes.(attr.name) in
        let field_access =
          let field_name = Longident.parse field.pld_name.txt in
          Ast.pexp_field ~loc (Ast.evar ~loc "t") (Loc.make ~loc field_name)
        in
        let serializer = serializer_for_type ~ctxt field.pld_type in
        [%expr field ctx [%e field_name] ([%e serializer] [%e field_access])])
      labels
  in

  let fields =
    List.fold_left
      (fun last curr ->
        [%expr
          let* () = [%e curr] in
          [%e last]])
      [%expr Ok ()] fields
  in

  [%expr record ctx [%e type_name] [%e field_count] (fun ctx -> [%e fields])]

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

  let typename = type_decl.ptype_name.txt in
  let type_attributes =
    Attributes.of_record_attributes type_decl.ptype_attributes
  in

  let body =
    match type_decl with
    | { ptype_kind = Ptype_record label_declarations; ptype_name; _ } ->
        gen_serialize_record_impl ~ctxt ptype_name type_attributes
          label_declarations
    | { ptype_kind = Ptype_variant cstrs_declaration; ptype_name; _ } ->
        gen_serialize_variant_impl ~ctxt ptype_name type_attributes
          cstrs_declaration
    | { 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 let [%p serializer_name] = Serde.Ser.(fun t ctx -> [%e body])]

let generate_impl ~ctxt (_rec_flag, type_declarations) =
  let loc = loc ~ctxt in
  [ [%stri let ( let* ) = Result.bind] ]
  @ 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 "serialize" ~str_type_decl:impl_generator
    ~sig_type_decl:intf_generator