package lablqml

  1. Overview
  2. Docs

Source file ppx_qt.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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
open Base
open Printf
open PpxQtCfg
open Gencpp
open Ppxlib
open Ppxlib.Ast_builder.Default
open TypeRepr

let make_coretyp ~loc txt = Ast_helper.Typ.constr ~loc { txt; loc } []

let cppobj_coretyp loc = make_coretyp ~loc (Lident "cppobj")

let unit_coretyp loc = make_coretyp ~loc (Lident "unit")

let int_coretyp loc = make_coretyp ~loc (Lident "int")

let string_coretyp loc = make_coretyp ~loc (Lident "string")

let make_store_func ~loc ~classname : structure_item =
  let pval_prim = [ sprintf "caml_store_value_in_%s" classname ] in
  let pval_type =
    [%type:
      t Lablqml.cppobj ->
      [%t ptyp_object ~loc [] Open] ->
      [%t ptyp_constr ~loc { txt = Lident "unit"; loc } []]]
  in
  pstr_primitive ~loc
  @@ value_description ~loc ~name:(Located.mk ~loc "store") ~type_:pval_type
       ~prim:pval_prim

let make_stub_general ~loc ~types ~name ~stub_name =
  let rec helper = function
    | [] -> assert false
    | [ t ] -> t
    | txt :: xs -> [%type: [%t txt] -> [%t helper xs]]
  in
  let type_ = helper types in
  pstr_primitive ~loc
  @@ value_description ~loc ~name:(Located.mk ~loc name) ~type_
       ~prim:[ stub_name ]

let make_creator ~loc ~classname =
  pstr_primitive ~loc
  @@ value_description ~loc
       ~name:(Located.mk ~loc @@ sprintf "create_%s" classname)
       ~type_:[%type: unit -> 'a]
       ~prim:[ sprintf "caml_create_%s" classname ]

let make_stub_for_signal ~classname ~loc ~typ name : structure_item =
  pstr_primitive ~loc
  @@ value_description ~loc
       ~name:(Located.mk ~loc @@ sprintf "stub_%s" name)
       ~type_:[%type: _ Lablqml.cppobj -> [%t typ] -> unit]
       ~prim:[ sprintf "caml_%s_%s_cppmeth_wrapper" classname name ]

let make_virt_meth ~loc ~name xs =
  let rec helper = function
    | [] -> assert false
    | [ t ] -> ptyp_constr ~loc { txt = TypeRepr.ocaml_ast_of_typ t; loc } []
    | t :: xs ->
        [%type:
          [%t
            ptyp_constr ~loc (Located.mk ~loc @@ TypeRepr.ocaml_ast_of_typ t) []] ->
          [%t helper xs]]
  in
  let typ = helper xs in
  pcf_method ~loc (Located.mk name ~loc, Public, Cfk_virtual typ)

let mkloc x loc = Located.mk ~loc x

let make_initializer ~loc : class_field =
  pcf_initializer ~loc [%expr store cppobj self]

let make_handler_meth ~loc : class_field =
  let e = [%expr cppobj] in
  pcf_method ~loc (Located.mk "handler" ~loc, Public, Cfk_concrete (Fresh, e))

let eval_meth_typ t =
  match TypeRepr.eval_meth_typ_gen t with
  | Result.Ok xs -> List.map ~f:snd xs
  | Error (msg, typ) -> raise @@ ErrorMsg (msg, typ)

let eval_signal_typ t =
  match TypeRepr.eval_meth_typ_gen t with
  | Result.Ok xs -> xs
  | Error (msg, typ) -> raise @@ ErrorMsg (msg, typ)

let check_meth_typ ~loc _xs =
  let _ = loc in
  (* TODO: some checks like unit type should be at the end of list *)
  (* TODO: check that modelindexes are not used without QAbstractItemModel base *)
  true

let wrap_meth ~classname (* ?(options = []) *)
    (({ txt = methname; loc }, _, kind) as m) =
  match kind with
  | Cfk_concrete _ ->
      raise @@ ErrorMsg ("Qt methods should be marked as virtual", loc)
  | Cfk_virtual typ ->
      let meth_typ = eval_meth_typ typ in
      if not (check_meth_typ ~loc meth_typ) then
        raise @@ ErrorMsg (sprintf "Method '%s' has wrong type" methname, loc);
      let () =
        if PpxQtCfg.config.gencpp then
          (* let options =
               if Options.is_itemmodel options then [ OItemModel ] else []
             in *)
          Gencpp.gen_meth (*~options*) ~classname ~methname
            (meth_typ :> Arg.non_cppobj Arg.t list)
      in
      [ pcf_method ~loc m ]

(*
(* in 4.03 definition have changed from string to Ast_types.arg_label *)
let oldify_arg_label = function
  | Nolabel -> ""
  | Labelled s -> s
  | Optional s -> s
;; *)

module OfClass = struct
  let run ~attributes loc (ci : class_declaration) =
    (* print_endline "wrap_class_type_decl on class type markend with `qtclass`"; *)
    let classname = ci.pci_name.txt in
    let options =
      List.concat
        [
          (if has_attr "itemmodel" attributes then [ OItemModel ] else []);
          (if has_attr "instantiable" attributes then [ OInstantiable ] else []);
        ]
    in
    if PpxQtCfg.config.gencpp then Gencpp.open_files ~options ~classname;
    let clas_sig =
      match ci.pci_expr.pcl_desc with
      | Pcl_structure s -> s
      | _ ->
          raise
          @@ ErrorMsg
               ("Qt class signature should be structure of class", ci.pci_loc)
    in
    let fields : class_field list = clas_sig.pcstr_fields in
    let heading = ref [ [%stri type t]; make_store_func ~classname ~loc ] in
    let wrap_signal ~options ~classname
        (({ txt = signalname; loc }, _, kind) as _m) =
      let _ = options in
      match kind with
      | Cfk_concrete _ ->
          raise
          @@ ErrorMsg ("We can generate prop methods for virtuals only", loc)
      | Cfk_virtual core_typ ->
          (* stub which will be called by OCaml meth*)
          let external_stub =
            let open Ast_builder.Default in
            pstr_primitive ~loc
            @@ value_description ~loc
                 ~name:(Located.mk ~loc @@ sprintf "stub_%s" signalname)
                 ~type_:[%type: t Lablqml.cppobj -> [%t core_typ]]
                 ~prim:
                   [ sprintf "caml_%s_%s_emitter_wrapper" classname signalname ]
          in
          Gencpp.ref_append ~set:heading external_stub;
          (* C++ stub *)
          let types = eval_signal_typ core_typ in
          let args, res = List.(drop_last_exn types, last_exn types) in
          if Stdlib.(snd res <> TypeRepr.Arg.Unit) then
            raise @@ ErrorMsg ("Result type for signal should be unit", loc);
          assert (Stdlib.(fst res = Nolabel));
          (* last argument always will be without a label, isn't it? *)
          if List.exists ~f:(fun (label, _) -> Stdlib.( = ) label Nolabel) args
          then raise @@ ErrorMsg ("All arguments should have a label", loc);
          if config.gencpp then
            Gencpp.gen_signal ~classname ~signalname
            @@ List.map ~f:(fun (l, x) -> (l, (x :> Arg.non_cppobj Arg.t))) args;
          (* OCaml meth *)
          let e =
            pexp_poly ~loc
              (pexp_apply ~loc
                 (pexp_ident ~loc
                    (Located.mk ~loc (Lident ("stub_" ^ signalname))))
                 [ (Nolabel, [%expr self#handler]) ])
              None
          in
          [
            pcf_method ~loc
              ( Located.mk ~loc ("emit_" ^ signalname),
                Public,
                Cfk_concrete (Fresh, e) );
          ]
    in
    let wrap_prop ~classname (loc, flag, kind) =
      let propname = loc.txt in
      let loc = loc.loc in
      match kind with
      | Cfk_concrete _ ->
          raise
          @@ ErrorMsg ("We can generate prop methods for virtuals only", loc)
      | Cfk_virtual core_typ -> (
          match type_suits_prop core_typ with
          | Ok typ ->
              if config.gencpp then Gencpp.gen_prop ~classname ~propname typ;
              let signal_name = Names.signal_of_prop propname in
              ref_append ~set:heading
                (make_stub_for_signal ~classname ~loc ~typ:core_typ signal_name);
              let e =
                pexp_poly ~loc
                  (pexp_apply ~loc
                     (pexp_ident ~loc
                        (Located.mk ~loc @@ lident ("stub_" ^ signal_name)))
                     [ (Nolabel, [%expr self#handler]) ])
                  None
              in
              [
                pcf_method ~loc
                  ( Located.mk ~loc ("emit_" ^ signal_name),
                    Public,
                    Cfk_concrete (Fresh, e) );
                pcf_method ~loc
                  ( Located.mk ~loc (Gencpp.Names.getter_of_prop propname),
                    flag,
                    Cfk_virtual
                      Ast_helper.Typ.(arrow Nolabel (unit_coretyp loc) core_typ)
                  );
              ]
          | Error msg ->
              raise
              @@ ErrorMsg
                   (sprintf "Can't wrap property '%s': %s" propname msg, loc))
    in
    let wrap_field (f_desc : class_field) : class_field list =
      match f_desc.pcf_desc with
      | Pcf_method m when has_attr "qtmeth" f_desc.pcf_attributes ->
          wrap_meth ~classname m
      | Pcf_method m when has_attr "qtsignal" f_desc.pcf_attributes ->
          wrap_signal ~options ~classname m
      | Pcf_method m when has_attr "qtprop" f_desc.pcf_attributes ->
          wrap_prop ~classname m
      | _ -> []
    in
    let ocaml_typ_of_typ cppobj_param =
      let open TypeRepr.Arg in
      let rec helper = function
        | Cppobj ->
            ptyp_constr ~loc
              (Located.mk ~loc @@ Ldot (Lident "Lablqml", "cppobj"))
              [ cppobj_param ]
        | QVariant -> [%type: QVariant.t]
        | QModelIndex -> [%type: QModelIndex.t]
        | Bool -> [%type: bool]
        | Unit -> [%type: unit]
        | QByteArray | QString -> [%type: string]
        | Int -> [%type: int]
        | QList x ->
            ptyp_constr ~loc (Located.mk ~loc @@ Lident "list") [ helper x ]
      in

      helper
    in
    let itemmodel_meths =
      if has_attr "itemmodel" attributes then (
        let f (methname, meth_typ, minfo) =
          (* printf "Generating itemmodel-specific meth: '%s'\n" methname; *)
          if config.gencpp then
            Gencpp.gen_meth ~classname ~methname ~minfo meth_typ
        in
        if config.gencpp then List.iter ~f Gencpp.itemmodel_members;
        (* now add some OCaml code *)
        let f (name, stub_name, xs) =
          let types = List.map xs ~f:(ocaml_typ_of_typ [%type: t]) in
          ref_append ~set:heading
          @@ make_stub_general ~loc ~types ~name:("stub_" ^ name) ~stub_name
        in
        List.iter (Gencpp.itemmodel_externals ~classname) ~f;
        let () = if config.gencpp then Gencpp.gen_itemmodel_stuff ~classname in
        let add_role_stub =
          let name = Located.mk ~loc "add_role" in
          let prim =
            [ sprintf "caml_%s_%s_cppmeth_wrapper" classname "addRole" ]
          in
          let type_ = [%type: 'a -> int -> string -> unit] in
          Ast_builder.Default.pstr_primitive ~loc
          @@ Ast_builder.Default.value_description ~loc ~name ~type_ ~prim
        in
        ref_append add_role_stub ~set:heading;

        let emitters =
          List.map
            [
              "dataChanged";
              "beginInsertRows";
              "endInsertRows";
              "beginRemoveRows";
              "endRemoveRows";
            ] ~f:(fun name ->
              let e =
                pexp_poly ~loc
                  (pexp_apply ~loc
                     (pexp_ident ~loc @@ Located.mk ~loc
                     @@ Lident ("stub_" ^ name))
                     [ (Nolabel, [%expr cppobj]) ])
                  None
              in

              pcf_method ~loc (mkloc name loc, Public, Cfk_concrete (Fresh, e)))
        in
        let virtuals =
          [
            make_virt_meth
              [ Arg.QModelIndex; Arg.QModelIndex ]
              ~loc ~name:"parent";
            make_virt_meth
              [ Arg.Int; Arg.Int; Arg.QModelIndex; Arg.QModelIndex ]
              ~loc ~name:"index";
            make_virt_meth [ Arg.QModelIndex; Arg.Int ] ~loc ~name:"columnCount";
            make_virt_meth [ Arg.QModelIndex; Arg.Int ] ~loc ~name:"rowCount";
            make_virt_meth
              [ Arg.QModelIndex; Arg.Bool ]
              ~loc ~name:"hasChildren";
            make_virt_meth
              [ Arg.QModelIndex; Arg.Int; Arg.QVariant ]
              ~loc ~name:"data";
          ]
        in
        emitters @ virtuals)
      else []
    in
    let new_fields = List.concat_map fields ~f:wrap_field @ itemmodel_meths in
    let new_fields =
      make_initializer ~loc :: make_handler_meth ~loc :: new_fields
    in
    let new_expr =
      let open Ast_builder.Default in
      pcl_fun ~loc Nolabel None [%pat? cppobj]
      @@ pcl_structure ~loc
      @@ class_structure ~self:[%pat? self] ~fields:new_fields
    in
    let ans = pstr_class ~loc [ { ci with pci_expr = new_expr } ] in
    let creator = make_creator ~loc ~classname in
    if config.gencpp then Gencpp.close_files ~options ();
    !heading @ [ ans; creator ]
end

let () =
  Ppxlib.Driver.register_transformation
    ~impl:(fun ss ->
      let m =
        object (self)
          inherit Ast_traverse.map as super

          method! structure ss =
            (* TODO: Maybe we don't need this *)
            List.concat @@ List.map ~f:self#do_structure_item ss

          method do_structure_item si =
            let ans =
              let open Ast_pattern in
              parse
                (alt
                   (pstr_module
                    @@ module_binding ~name:(some __)
                         ~expr:
                           (pmod_constraint
                              (pmod_attributes __ @@ pmod_structure __)
                              (pmty_signature __))
                   |> pack3
                   |> map2 ~f:(fun (name, attrs, stru) sign ->
                          match find_attr ~name:"qml" attrs with
                          | Some (PStr [ { pstr_desc = Pstr_eval (e, _) } ])
                            -> (
                              match Myparser.Testdemo.parse_singleton e with
                              | None ->
                                  raise
                                    (ErrorMsg ("bad attribute", si.pstr_loc))
                              | Some info ->
                                  Generation2.wrap_module_decl ~loc:si.pstr_loc
                                    name stru sign info)
                          | Some _ ->
                              raise
                                (ErrorMsg
                                   ( sprintf "bad attribute %s %d" __FILE__
                                       __LINE__,
                                     si.pstr_loc ))
                          | None -> [ super#structure_item si ]))
                   (pstr_class (__ ^:: nil)
                   |> map1 ~f:(fun cinfo ->
                          if has_attr "qtclass" cinfo.pci_attributes then
                            Ast_helper.with_default_loc si.pstr_loc (fun () ->
                                OfClass.run ~attributes:cinfo.pci_attributes
                                  si.pstr_loc cinfo)
                          else [ super#structure_item si ])))
                si.pstr_loc
                ~on_error:(fun () -> [ super#structure_item si ])
                si Fun.id
            in
            ans
        end
      in
      m#structure ss)
    "ppx_qt"
OCaml

Innovation. Community. Security.