File: ppx_have.ml

package info (click to toggle)
ocaml-extunix 0.4.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 624 kB
  • sloc: ml: 3,278; ansic: 3,245; makefile: 24
file content (176 lines) | stat: -rw-r--r-- 5,455 bytes parent folder | download
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
let all = ref false
let funcs = Hashtbl.create 16

let args_spec =
  [
    ("--gen-all", Arg.Set all, "generate values from all [%%have ...] sections");
  ]

module ExtUnixConfig = Config
open Ppxlib

let check ~loc name =
  match ExtUnixConfig.feature name with
  | None -> Location.raise_errorf ~loc "Unregistered feature %s" name
  | Some have -> have

let ident x = Ocaml_common.Location.mknoloc (lident x)

(* Evaluating conditions *)

let atom_of_expr ~loc expr =
  match expr.pexp_desc with
  | Pexp_construct ({ txt = Longident.Lident x; _ }, None) -> x
  | _ -> Location.raise_errorf ~loc "have: atom_of_expr"

let conj_of_expr ~loc expr =
  match expr.pexp_desc with
  | Pexp_construct _ -> [ atom_of_expr ~loc expr ]
  | Pexp_tuple args -> List.map (atom_of_expr ~loc) args
  | _ -> Location.raise_errorf ~loc "have: conj_of_expr"

let disj_of_expr ~loc expr =
  match expr.pexp_desc with
  | Pexp_construct _ -> [ [ atom_of_expr ~loc expr ] ]
  | Pexp_tuple args -> List.map (conj_of_expr ~loc) args
  | _ -> Location.raise_errorf ~loc "have: disj_of_expr"

let eval_cond ~loc cond =
  match cond.pstr_desc with
  | Pstr_eval (expr, _attributes) ->
      List.exists (List.for_all (check ~loc)) (disj_of_expr ~loc expr)
  | _ -> Location.raise_errorf ~loc "have: eval_cond"

(* have rule *)

let invalid_external ~loc =
  let open Ast_builder.Default in
  let rec make_dummy_f ~loc body typ =
    match typ.ptyp_desc with
    | Ptyp_arrow (l, arg, ret) ->
        let arg =
          match l with Optional _ -> [%type: [%t arg] option] | _ -> arg
        in
        let e = make_dummy_f ~loc body ret in
        pexp_fun ~loc l None [%pat? (_ : [%t arg])] e
    | _ -> [%expr ([%e body] : [%t typ])]
  in
  let raise_not_available ~loc x =
    let e = pexp_constant ~loc (Pconst_string (x, loc, None)) in
    [%expr raise (Not_available [%e e])]
  in
  let externals_of =
    object
      inherit Ast_traverse.map as super

      method! structure_item x =
        match x.pstr_desc with
        | Pstr_primitive p ->
            let body = raise_not_available ~loc p.pval_name.txt in
            let expr = make_dummy_f ~loc body p.pval_type in
            let pat = ppat_var ~loc p.pval_name in
            let vb = value_binding ~loc ~pat ~expr in
            let vb =
              { vb with pvb_attributes = p.pval_attributes @ vb.pvb_attributes }
            in
            pstr_value ~loc Nonrecursive [ vb ]
        | _ -> super#structure_item x
    end
  in
  externals_of#structure_item

let record_external have =
  let externals_of =
    object
      inherit Ast_traverse.iter as super

      method! structure_item x =
        match x.pstr_desc with
        | Pstr_primitive p -> Hashtbl.replace funcs p.pval_name.txt have
        | _ -> super#structure_item x
    end
  in
  externals_of#structure_item

let have_constr ~loc =
  let have_constr =
    object
      inherit Ast_traverse.map as super

      method! constructor_declaration x =
        match super#constructor_declaration x with
        | {
            pcd_attributes =
              [
                {
                  attr_name = { txt = "have"; _ };
                  attr_payload = PStr (cond :: _);
                  _;
                };
              ];
            _;
          } as x ->
            if eval_cond ~loc cond then x
            else
              {
                x with
                pcd_name =
                  { x.pcd_name with txt = x.pcd_name.txt ^ "__Not_available" };
              }
        | x -> x
    end
  in
  have_constr#structure_item

let have_expand ~ctxt cond items =
  let loc = Expansion_context.Extension.extension_point_loc ctxt in
  let have = eval_cond ~loc cond in
  List.iter (record_external have) items;
  match (have, !all) with
  | true, true -> items
  | true, false -> List.map (have_constr ~loc) items
  | false, true -> List.map (invalid_external ~loc) items
  | false, false -> []

let have_extension =
  Extension.V3.declare_inline "have" Extension.Context.structure_item
    Ast_pattern.(pstr (__ ^:: __))
    have_expand

let have_rule = Context_free.Rule.extension have_extension

(* show_me_the_money rule *)

let show_me_the_money_expand ~ctxt doc =
  let loc = Expansion_context.Extension.extension_point_loc ctxt in
  let open Ast_builder.Default in
  let make_have () =
    Hashtbl.fold
      (fun func have acc ->
        let lhs = ppat_constant ~loc (Pconst_string (func, loc, None)) in
        let e = pexp_construct ~loc (ident (string_of_bool have)) None in
        case ~lhs ~guard:None ~rhs:[%expr Some [%e e]] :: acc)
      funcs
      [ case ~lhs:[%pat? _] ~guard:None ~rhs:[%expr None] ]
  in
  if !all then
    let expr = pexp_function ~loc (make_have ()) in
    let pat = ppat_var ~loc (Ocaml_common.Location.mknoloc "have") in
    let vb = value_binding ~loc ~pat ~expr in
    let vb = { vb with pvb_attributes = doc :: vb.pvb_attributes } in
    [ pstr_value ~loc Nonrecursive [ vb ] ]
  else []

let show_me_the_money_extension =
  Extension.V3.declare_inline "show_me_the_money"
    Extension.Context.structure_item
    Ast_pattern.(pstr (pstr_attribute __ ^:: nil))
    show_me_the_money_expand

let show_me_the_money_rule =
  Context_free.Rule.extension show_me_the_money_extension

let () =
  List.iter (fun (key, spec, doc) -> Driver.add_arg key spec ~doc) args_spec;
  let rules = [ have_rule; show_me_the_money_rule ] in
  Driver.register_transformation ~rules "ppx_have"