package streamable

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file helpers.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
open! Base
open! Import

let pat_var ~loc name = ppat_var ~loc (Loc.make ~loc name)
let exp_var ~loc name = pexp_ident ~loc (Loc.make ~loc (lident name))
let unsupported_use ~loc ~why = Location.raise_errorf ~loc "ppx_streamable: %s." why

let get_the_one_and_only_type_t type_decs ~loc =
  match type_decs with
  | [ type_dec ] ->
    if String.(type_dec.ptype_name.txt <> "t")
    then unsupported_use ~loc ~why:"only types named [t] are supported"
    else type_dec
  | _ -> unsupported_use ~loc ~why:"mutually-recursive types are not supported"
;;

let apply_streamable_dot ({ loc; rpc; version } : Ctx.t) ~functor_name ~arguments =
  let functor_name =
    match rpc with
    | false -> functor_name
    | true -> [%string "%{functor_name}_rpc"]
  in
  let functor_ =
    pmod_ident
      ~loc
      (Loc.make
         ~loc
         (Longident.parse
            [%string "Streamable.Stable.%{Version.module_name version}.%{functor_name}"]))
  in
  List.fold arguments ~init:functor_ ~f:(fun accum argument ->
    pmod_apply ~loc accum argument)
;;

let split_longident longident =
  match List.rev (Longident.flatten_exn longident) with
  | [] -> invalid_arg "Ppxlib.Longident.flatten"
  | [ last ] -> `prefix None, `last last
  | last :: reversed_prefix ->
    let prefix =
      reversed_prefix |> List.rev |> String.concat ~sep:"." |> Longident.parse
    in
    `prefix (Some prefix), `last last
;;

let if_module_dot_t_then_module' longident =
  match split_longident longident with
  | `prefix (Some module_), `last last when String.(last = "t") -> Some module_
  | _ -> None
;;

let if_module_dot_t_then_module core_type =
  match core_type.ptyp_desc with
  | Ptyp_constr (longident_loc, _) ->
    (match if_module_dot_t_then_module' longident_loc.txt with
     | None -> None
     | Some longident -> Some { longident_loc with txt = longident })
  | _ -> None
;;

let longident_is_like_t longident ~primitive_name ~first_module_name =
  let is_like_primitive () =
    match primitive_name with
    | None -> false
    | Some primitive_name ->
      (match longident with
       | Lident lident -> String.(primitive_name = lident)
       | _ -> false)
  in
  let is_like_module () =
    match if_module_dot_t_then_module' longident with
    | None -> false
    | Some longident ->
      (match Longident.flatten_exn longident with
       | first :: _ -> String.(first_module_name = first)
       | _ -> false)
  in
  is_like_primitive () || is_like_module ()
;;

let core_type_with_atomic_attribute ~loc ~module_dot_t =
  let core_type = ptyp_constr ~loc (Loc.make ~loc (Longident.parse module_dot_t)) [] in
  { core_type with
    ptyp_attributes =
      [ { attr_name = Loc.make ~loc (Attribute.name Attributes.atomic)
        ; attr_payload = PStr []
        ; attr_loc = loc
        }
      ]
  }
;;

let to_streamable ~loc ~body = [%stri let to_streamable = [%e body]]
let of_streamable ~loc ~body = [%stri let of_streamable = [%e body]]

let streamable_of_streamable
  ?type_t
  ctx
  ~streamable_module
  ~to_streamable_fun
  ~of_streamable_fun
  =
  let loc = ctx.Ctx.loc in
  apply_streamable_dot
    ctx
    ~functor_name:"Of_streamable"
    ~arguments:
      [ streamable_module
      ; pmod_structure
          ~loc
          [ Option.value type_t ~default:[%stri type nonrec t = t]
          ; to_streamable ~loc ~body:to_streamable_fun
          ; of_streamable ~loc ~body:of_streamable_fun
          ]
      ]
;;

let name_of_num i ~starting_letter =
  assert (i >= 0);
  let q = i / 26 in
  let r = i % 26 in
  let c = Char.of_int_exn (r + Char.to_int starting_letter) in
  let s = String.of_char c in
  if q = 0 then s else s ^ Int.to_string q
;;

let lowercase_name_of_num = name_of_num ~starting_letter:'a'
let uppercase_name_of_num = name_of_num ~starting_letter:'A'

let type_declaration_match
  type_
  ~payload
  ~streamable_module
  ~to_streamable_fun
  ~of_streamable_fun
  ~children
  =
  match (type_ : Type_.t) with
  | Core_type (_ : core_type) -> None
  | Type_declaration type_dec ->
    let%map payload = payload type_dec in
    let children_types = children ~loc:(Type_.loc type_) ~payload in
    ({ children = List.map ~f:Type_.core_type children_types
     ; apply_functor =
         (fun ctx children_modules ->
           let loc = ctx.loc in
           let children = List.zip_exn children_types children_modules in
           streamable_of_streamable
             ctx
             ~streamable_module:(streamable_module ctx children)
             ~to_streamable_fun:(to_streamable_fun ~loc ~payload)
             ~of_streamable_fun:(of_streamable_fun ~loc ~payload))
     }
      : Clause.Match.t)
;;

let polymorphic_primitive_or_module_match
  ~num_type_parameters
  ~primitive_name
  ~first_module_name
  type_
  (_ : Ctx.t)
  =
  let%bind core_type = Type_.match_core_type type_ in
  let%map type_parameters =
    match core_type.ptyp_desc with
    | Ptyp_constr (longident_loc, type_parameters) ->
      (match longident_is_like_t longident_loc.txt ~primitive_name ~first_module_name with
       | false -> None
       | true ->
         assert (List.length type_parameters = num_type_parameters);
         Some type_parameters)
    | _ -> None
  in
  ({ children = List.map type_parameters ~f:Type_.core_type
   ; apply_functor =
       (fun ctx children ->
         apply_streamable_dot
           ctx
           ~functor_name:[%string "Of_%{String.lowercase first_module_name}"]
           ~arguments:children)
   }
    : Clause.Match.t)
;;

let module_name_for_type_parameter = function
  | `Ptyp_var name -> String.capitalize name
  | `Ptyp_any index -> [%string "Unnamed_type_parameter%{index#Int}"]
;;

let make_streamable = "Make_streamable"

let streamable_module_type ~loc ~rpc =
  pmty_ident
    ~loc
    (Loc.make ~loc (Longident.parse (if rpc then "Streamable.S_rpc" else "Streamable.S")))
;;
OCaml

Innovation. Community. Security.