-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathppx_have.ml
176 lines (152 loc) · 5.33 KB
/
ppx_have.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
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"