Skip to content

Commit 562a9fa

Browse files
Merge pull request #518 from patricoferris/ast-docs
Ast_builder documentation
2 parents a4004e2 + 0710e7b commit 562a9fa

File tree

4 files changed

+186
-26
lines changed

4 files changed

+186
-26
lines changed

CHANGES.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ details.
1414

1515
### Other changes
1616

17-
- Support class type declarations in derivers with the new, optional arguments
18-
`{str,sig}_class_type_decl` in `Deriving.add` (#538, @patricoferris)
19-
2017
- Fix `deriving_inline` round-trip check so that it works with 5.01 <-> 5.02
2118
migrations (#519, @NathanReb)
2219

@@ -28,6 +25,11 @@ details.
2825
to what the compiler's `-dparsetree` is.
2926
(#530, @NathanReb)
3027

28+
- Add Parsetree documentation comments to `Ast_builder` functions (#518, @patricoferris)
29+
30+
- Support class type declarations in derivers with the new, optional arguments
31+
`{str,sig}_class_type_decl` in `Deriving.add` (#538, @patricoferris)
32+
3133
0.33.0 (2024-07-22)
3234
-------------------
3335

src/ast_builder_intf.ml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,6 @@ type 'a with_location = loc:Location.t -> 'a
142142

143143
module type S = sig
144144
module Located : Located with type 'a with_loc := 'a without_location
145-
146-
include module type of Ast_builder_generated.Make (struct
147-
let loc = Location.none
148-
end)
149-
145+
include Ast_builder_generated.Intf_located
150146
include Additional_helpers with type 'a with_loc := 'a without_location
151147
end

src/gen/gen_ast_builder.ml

Lines changed: 171 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
11
open Import
22
open Ast_helper
33
open Printf
4+
module Section_map = String.Map
5+
6+
let section_map_of_assoc items =
7+
List.fold_left
8+
~f:(fun acc (name, v) ->
9+
match Section_map.find_opt name acc with
10+
| None -> Section_map.add name [ v ] acc
11+
| Some vs -> Section_map.add name (v :: vs) acc)
12+
~init:Section_map.empty items
13+
14+
let doc_comment_from_attribue (attr : attribute) =
15+
match attr.attr_name.txt with
16+
| "ocaml.doc" -> (
17+
match attr.attr_payload with
18+
| PStr
19+
[
20+
{
21+
pstr_desc =
22+
Pstr_eval
23+
({ pexp_desc = Pexp_constant (Pconst_string (s, _, _)); _ }, _);
24+
_;
25+
};
26+
] ->
27+
Some s
28+
| _ -> None)
29+
| _ -> None
30+
31+
let doc_comment ~node_name ~function_name attributes =
32+
let parsetree_comment =
33+
List.find_map ~f:doc_comment_from_attribue attributes
34+
in
35+
let pp_parsetree_comment ppf = function
36+
| None -> ()
37+
| Some pc -> Format.fprintf ppf "{b Example OCaml}\n\n%s" pc
38+
in
39+
Format.asprintf "[%s] constructs an {! Ast.%s}\n\n%a" function_name node_name
40+
pp_parsetree_comment parsetree_comment
441

542
let prefix_of_record lds =
643
common_prefix (List.map lds ~f:(fun ld -> ld.pld_name.txt))
@@ -11,8 +48,25 @@ end) =
1148
struct
1249
open Fixed_loc
1350

51+
let core_type_of_return_type (typ : type_declaration) =
52+
let typ_name = typ.ptype_name.txt in
53+
let typ_name =
54+
match List.rev (String.split_on_char ~sep:'_' typ_name) with
55+
| "desc" :: _ ->
56+
String.sub ~pos:0 ~len:(String.length typ_name - 5) typ_name
57+
| _ -> typ_name
58+
in
59+
match typ.ptype_params with
60+
| [] -> M.ctyp "%s" typ_name
61+
| params ->
62+
let params =
63+
List.map params ~f:(fun (ctyp, _) -> Format.asprintf "%a" A.ctyp ctyp)
64+
in
65+
M.ctyp "(%s) %s" (String.concat ~sep:", " params) typ_name
66+
1467
let gen_combinator_for_constructor
15-
~wrapper:(wpath, wprefix, has_attrs, has_loc_stack) path ~prefix cd =
68+
~wrapper:(wpath, wprefix, has_attrs, has_loc_stack) path ~prefix
69+
return_type cd =
1670
match cd.pcd_args with
1771
| Pcstr_record _ ->
1872
(* TODO. *)
@@ -66,31 +120,47 @@ struct
66120
let body =
67121
if fixed_loc then body else M.expr "fun ~loc -> %a" A.expr body
68122
in
69-
M.stri "let %a = %a" A.patt
70-
(pvar (function_name_of_id ~prefix cd.pcd_name.txt))
71-
A.expr body
123+
let function_name = function_name_of_id ~prefix cd.pcd_name.txt in
124+
let pvar_function_name = pvar function_name in
125+
let str = M.stri "let %a = %a" A.patt pvar_function_name A.expr body in
126+
let return_type = core_type_of_return_type return_type in
127+
let typ =
128+
List.fold_right cd_args ~init:return_type ~f:(fun cty acc ->
129+
M.ctyp "%a -> %a" A.ctyp cty A.ctyp acc)
130+
in
131+
let typ =
132+
if fixed_loc then typ else M.ctyp "loc:Location.t -> %a" A.ctyp typ
133+
in
134+
let sign =
135+
M.sigi "val %a : %a (** %s *)" A.patt pvar_function_name A.ctyp typ
136+
(doc_comment ~function_name ~node_name:cd.pcd_name.txt
137+
cd.pcd_attributes)
138+
in
139+
(str, (Format.asprintf "%a" A.ctyp return_type, sign))
72140

73-
let gen_combinator_for_record path ~prefix lds =
141+
let gen_combinator_for_record path ~prefix return_type lds =
74142
let fields =
75143
List.map lds ~f:(fun ld -> fqn_longident path ld.pld_name.txt)
76144
in
77145
let funcs =
78146
List.map lds ~f:(fun ld ->
79-
map_keyword (without_prefix ~prefix ld.pld_name.txt))
147+
(ld.pld_type, map_keyword (without_prefix ~prefix ld.pld_name.txt)))
80148
in
81149
let body =
82150
Exp.record
83-
(List.map2 fields funcs ~f:(fun field func ->
151+
(List.map2 fields funcs ~f:(fun field (_, func) ->
84152
( Loc.mk field,
85153
if func = "attributes" then M.expr "[]" else evar func )))
86154
None
87155
in
88156
let body =
89-
let l = List.filter funcs ~f:(fun f -> f <> "loc" && f <> "attributes") in
157+
let l =
158+
List.filter funcs ~f:(fun (_, f) -> f <> "loc" && f <> "attributes")
159+
in
90160
match l with
91-
| [ x ] -> Exp.fun_ Nolabel None (pvar x) body
161+
| [ (_, x) ] -> Exp.fun_ Nolabel None (pvar x) body
92162
| _ ->
93-
List.fold_right l ~init:body ~f:(fun func acc ->
163+
List.fold_right l ~init:body ~f:(fun (_, func) acc ->
94164
Exp.fun_ (Labelled func) None (pvar func) acc)
95165
in
96166
(* let body =
@@ -99,12 +169,39 @@ struct
99169
else
100170
body
101171
in*)
172+
let has_loc_field =
173+
List.exists ~f:(function _, "loc" -> true | _ -> false) funcs
174+
in
102175
let body =
103-
if List.mem "loc" ~set:funcs && not fixed_loc then
104-
M.expr "fun ~loc -> %a" A.expr body
176+
if has_loc_field && not fixed_loc then M.expr "fun ~loc -> %a" A.expr body
105177
else body
106178
in
107-
M.stri "let %a = %a" A.patt (pvar (function_name_of_path path)) A.expr body
179+
let return_ctyp = core_type_of_return_type return_type in
180+
let typ =
181+
let l =
182+
List.filter funcs ~f:(fun (_, f) -> f <> "loc" && f <> "attributes")
183+
in
184+
match l with
185+
| [ (c, _) ] -> M.ctyp "%a -> %a" A.ctyp c A.ctyp return_ctyp
186+
| _ ->
187+
List.fold_right l ~init:return_ctyp ~f:(fun (typ, func) acc ->
188+
M.ctyp "%s:%a -> %a" func A.ctyp typ A.ctyp acc)
189+
in
190+
let typ =
191+
if has_loc_field && not fixed_loc then
192+
M.ctyp "loc:Location.t -> %a" A.ctyp typ
193+
else typ
194+
in
195+
let pvar_function_name = pvar (function_name_of_path path) in
196+
let str = M.stri "let %a = %a" A.patt pvar_function_name A.expr body in
197+
let sign =
198+
M.sigi "val %a : %a (** %s *)" A.patt pvar_function_name A.ctyp typ
199+
(doc_comment
200+
~function_name:(function_name_of_path path)
201+
~node_name:(Format.asprintf "%a" A.ctyp return_ctyp)
202+
return_type.ptype_attributes)
203+
in
204+
(str, (Format.asprintf "%a" A.ctyp return_ctyp, sign))
108205

109206
let gen_td ?wrapper path td =
110207
if is_loc path then []
@@ -117,11 +214,11 @@ struct
117214
let prefix =
118215
common_prefix (List.map cds ~f:(fun cd -> cd.pcd_name.txt))
119216
in
120-
List.map cds ~f:(fun cd ->
121-
gen_combinator_for_constructor ~wrapper path ~prefix cd))
217+
List.map cds
218+
~f:(gen_combinator_for_constructor ~wrapper path ~prefix td))
122219
| Ptype_record lds ->
123220
let prefix = prefix_of_record lds in
124-
[ gen_combinator_for_record path ~prefix lds ]
221+
[ gen_combinator_for_record path ~prefix td lds ]
125222
| Ptype_abstract | Ptype_open -> []
126223
end
127224

@@ -140,6 +237,26 @@ let dump fn ~ext printer x =
140237
Format.fprintf ppf "%a@." printer x;
141238
close_out oc
142239

240+
let floating_comment s =
241+
let doc =
242+
PStr
243+
[
244+
{
245+
pstr_desc =
246+
Pstr_eval
247+
( {
248+
pexp_desc = Pexp_constant (Pconst_string (s, loc, None));
249+
pexp_loc = loc;
250+
pexp_loc_stack = [];
251+
pexp_attributes = [];
252+
},
253+
[] );
254+
pstr_loc = loc;
255+
};
256+
]
257+
in
258+
Sig.attribute (Attr.mk { txt = "ocaml.text"; loc } doc)
259+
143260
let generate filename =
144261
(* let fn = Misc.find_in_path_uncap !Config.load_path (unit ^ ".cmi") in*)
145262
let types = get_types ~filename in
@@ -196,10 +313,44 @@ let generate filename =
196313
path' td')
197314
|> List.flatten
198315
in
316+
let mod_items b = items b |> List.map ~f:fst in
317+
let mod_sig_items b = items b |> List.map ~f:snd |> section_map_of_assoc in
318+
let mk_intf ~name located =
319+
let ident : label with_loc = { txt = name; loc } in
320+
let longident = { txt = Lident name; loc } in
321+
let documented_items =
322+
Section_map.fold
323+
(fun label items acc ->
324+
let label =
325+
match String.split_on_char ~sep:'_' label with
326+
| [] -> assert false
327+
| l :: rest ->
328+
let bs = Bytes.of_string l in
329+
Bytes.set bs 0 (Char.uppercase_ascii @@ Bytes.get bs 0);
330+
String.concat ~sep:" " (Bytes.to_string bs :: rest)
331+
in
332+
(floating_comment (Format.asprintf "{2 %s}" label) :: items) @ acc)
333+
(mod_sig_items located) []
334+
in
335+
let items =
336+
if located then M.sigi "val loc : Location.t" :: documented_items
337+
else documented_items
338+
in
339+
let intf = Str.modtype (Mtd.mk ~typ:(Mty.signature items) ident) in
340+
(longident, intf)
341+
in
342+
let intf_name, intf = mk_intf ~name:"Intf" false in
343+
let intf_located_name, intf_located = mk_intf ~name:"Intf_located" true in
199344
let st =
200345
[
201346
Str.open_ (Opn.mk (Mod.ident (Loc.lident "Import")));
202-
Str.module_ (Mb.mk (Loc.mk (Some "M")) (Mod.structure (items false)));
347+
intf;
348+
intf_located;
349+
Str.module_
350+
(Mb.mk (Loc.mk (Some "M"))
351+
(Mod.constraint_
352+
(Mod.structure (mod_items false))
353+
(Mty.ident intf_name)));
203354
Str.module_
204355
(Mb.mk (Loc.mk (Some "Make"))
205356
(Mod.functor_
@@ -208,7 +359,9 @@ let generate filename =
208359
Mty.signature
209360
[ Sig.value (Val.mk (Loc.mk "loc") (M.ctyp "Location.t")) ]
210361
))
211-
(Mod.structure (M.stri "let loc = Loc.loc" :: items true))));
362+
(Mod.constraint_
363+
(Mod.structure (M.stri "let loc = Loc.loc" :: mod_items true))
364+
(Mty.ident intf_located_name))));
212365
]
213366
in
214367
dump "ast_builder_generated" Pprintast.structure st ~ext:".ml"

src/gen/import.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ module M = struct
120120
let patt fmt = parse Parse.pattern fmt
121121
let ctyp fmt = parse Parse.core_type fmt
122122
let str fmt = parse Parse.implementation fmt
123+
let sign fmt = parse Parse.interface fmt
123124

124125
let stri fmt =
125126
Format.kasprintf
@@ -128,6 +129,14 @@ module M = struct
128129
| [ x ] -> x
129130
| _ -> assert false)
130131
fmt
132+
133+
let sigi fmt =
134+
Format.kasprintf
135+
(fun s ->
136+
match Parse.interface (Lexing.from_string s) with
137+
| [ x ] -> x
138+
| _ -> failwith ("Failed to parse: " ^ s))
139+
fmt
131140
end
132141

133142
(* Antiquotations *)

0 commit comments

Comments
 (0)