Skip to content

Commit c6c6060

Browse files
authored
generate modules inside anonymous prelude module (#10)
* generate modules inside anonymous prelude module * proper order of prelude struct items
1 parent 4bc1a46 commit c6c6060

File tree

5 files changed

+354
-85
lines changed

5 files changed

+354
-85
lines changed

MIKMATCH.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ let do_something = function%mikmatch
8888
| _ -> ...
8989
```
9090

91+
You can also reference patterns from modules and vice versa:
92+
```ocaml
93+
module Patterns = struct
94+
let%mikmatch hex = {| ['0'-'9' 'a'-'f']+ |}
95+
end
96+
97+
let%mikmatch hex_with_prefix = {| "0x" Patterns.hex |}
98+
99+
module MorePatterns = struct
100+
let%mikmatch multiple_hexes = {| Patterns.hex+ |}
101+
let%mikmatch multiple_prefixed_hexes = {| hex_with_prefix+ |}
102+
end
103+
```
104+
91105
### Variable capture
92106
```ocaml
93107
let%mikmatch num = {| digit+ |}

src/ppx_mikmatch.ml

Lines changed: 139 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,23 @@
1717
open Ppxlib
1818
open Ast_builder.Default
1919

20+
let pvar ~loc name = ppat_var ~loc { txt = name; loc }
21+
let evar ~loc name = pexp_ident ~loc { txt = Lident name; loc }
22+
let rec list_take n = function [] -> [] | x :: xs -> if n <= 0 then [] else x :: list_take (n - 1) xs
23+
let list_is_empty = function [] -> true | _ -> false
24+
2025
let make_alias_binding ~loc ~var_name =
21-
let warning_attr =
22-
attribute ~loc ~name:{ txt = "warning"; loc } ~payload:(PStr [ { pstr_desc = Pstr_eval (estring ~loc "-32", []); pstr_loc = loc } ])
23-
in
24-
{
25-
pvb_pat = ppat_var ~loc { txt = var_name; loc };
26-
pvb_expr = pexp_ident ~loc { txt = Lident var_name; loc };
27-
pvb_constraint = None;
28-
pvb_attributes = [ warning_attr ];
29-
pvb_loc = loc;
30-
}
26+
let pat = pvar ~loc var_name in
27+
let expr = evar ~loc var_name in
28+
match [%stri let[@warning "-32"] [%p pat] = [%e expr]] with { pstr_desc = Pstr_value (_, [ vb ]); _ } -> vb | _ -> assert false
29+
30+
type binding_location =
31+
| TopLevel
32+
| InModule of string list (* module path *)
3133

3234
let transformation =
3335
object (self)
34-
inherit [value_binding list] Ast_traverse.fold_map as super
36+
inherit [(binding_location * value_binding) list] Ast_traverse.fold_map as super
3537

3638
method! structure_item item acc =
3739
match item.pstr_desc with
@@ -63,14 +65,14 @@ let transformation =
6365
let type_name = td.ptype_name.txt in
6466
let items, binding = Transformations.transform_type ~loc rec_flag type_name pattern_str td in
6567
let alias = pstr_value ~loc Nonrecursive [ make_alias_binding ~loc ~var_name:type_name ] in
66-
(alias :: items_acc) @ items, binding :: bindings_acc
68+
(alias :: items_acc) @ items, (TopLevel, binding) :: bindings_acc
6769
| _ -> items_acc, bindings_acc)
6870
([], acc) type_decls
6971
in
7072

7173
let wrapped = pstr_include ~loc:item.pstr_loc (include_infos ~loc:item.pstr_loc (pmod_structure ~loc:item.pstr_loc all_items)) in
7274
wrapped, all_bindings)
73-
(* let%mikmatch x = {|some regex|}*)
75+
(* let%mikmatch ... = {| ... |}*)
7476
| Pstr_extension (({ txt = "mikmatch"; _ }, PStr [ { pstr_desc = Pstr_value (rec_flag, vbs); _ } ]), _) ->
7577
let processed_vbs, collected_bindings =
7678
List.fold_left
@@ -80,19 +82,21 @@ let transformation =
8082
| Ppat_var { txt = var_name; _ }, Pexp_constant (Pconst_string (_, loc, _)) ->
8183
let binding = Transformations.transform_let ~loc vb in
8284
let alias = make_alias_binding ~loc ~var_name in
83-
alias :: vbs_acc, binding :: bindings_acc
85+
alias :: vbs_acc, (TopLevel, binding) :: bindings_acc
8486
(* destructuring - let%mikmatch {|/pattern/|} = expr *)
8587
| Ppat_constant (Pconst_string (pattern_str, _, _)), _ ->
8688
let new_vb, new_bindings = Transformations.transform_destructuring_let ~loc:vb.pvb_loc pattern_str vb.pvb_expr in
87-
new_vb :: vbs_acc, new_bindings @ bindings_acc
89+
new_vb :: vbs_acc, List.map (fun b -> TopLevel, b) new_bindings @ bindings_acc
8890
| _ -> vbs_acc, bindings_acc
8991
end
9092
([], acc) vbs
9193
in
9294

9395
let new_item = { item with pstr_desc = Pstr_value (rec_flag, List.rev processed_vbs) } in
9496
new_item, collected_bindings
95-
(* let x = expression (which might contain %mik/%pcre) *)
97+
(* let ... = expression (which might contain %mikmatch)
98+
e.g. let ... = {%mikmatch| ... |}
99+
*)
96100
| Pstr_value (rec_flag, vbs) ->
97101
let processed_vbs, collected_bindings =
98102
List.fold_left
@@ -105,7 +109,7 @@ let transformation =
105109
let alias =
106110
match vb.pvb_pat.ppat_desc with Ppat_var { txt = var_name; loc } -> make_alias_binding ~loc ~var_name | _ -> new_vb
107111
in
108-
alias :: vbs_acc, binding :: bindings_acc
112+
alias :: vbs_acc, (TopLevel, binding) :: bindings_acc
109113
| _ ->
110114
let new_expr, new_bindings = self#expression vb.pvb_expr bindings_acc in
111115
let new_vb = { vb with pvb_expr = new_expr } in
@@ -114,6 +118,33 @@ let transformation =
114118
in
115119
let new_item = { item with pstr_desc = Pstr_value (rec_flag, List.rev processed_vbs) } in
116120
new_item, collected_bindings
121+
(* module M = struct ... end which might contain %mikmatch defns *)
122+
| Pstr_module { pmb_name = { txt = Some mod_name; _ } as name; pmb_expr; pmb_attributes; pmb_loc } -> begin
123+
match pmb_expr.pmod_desc with
124+
| Pmod_structure mod_items ->
125+
let mod_items', mod_bindings = self#structure mod_items [] in
126+
if mod_bindings = [] then super#structure_item item acc
127+
else (
128+
let tagged_bindings =
129+
List.map
130+
(fun (loc, vb) -> match loc with InModule path -> InModule (mod_name :: path), vb | TopLevel -> InModule [ mod_name ], vb)
131+
mod_bindings
132+
in
133+
134+
(* include the module from the prelude struct, but only if we tagged bingings, then keep other original items *)
135+
let include_item =
136+
pstr_include ~loc:pmb_loc (include_infos ~loc:pmb_loc (pmod_ident ~loc:pmb_loc { txt = Lident mod_name; loc = pmb_loc }))
137+
in
138+
let new_items = if list_is_empty tagged_bindings then mod_items' else include_item :: mod_items' in
139+
140+
let alias_module =
141+
pstr_module ~loc:pmb_loc { pmb_name = name; pmb_expr = pmod_structure ~loc:pmb_loc new_items; pmb_attributes; pmb_loc }
142+
in
143+
alias_module, tagged_bindings @ acc)
144+
| _ ->
145+
(* other module types, no transformation needed *)
146+
super#structure_item item acc
147+
end
117148
| _ -> super#structure_item item acc
118149

119150
method! expression e_ext acc =
@@ -130,21 +161,21 @@ let transformation =
130161
begin match e.pexp_desc with
131162
| Pexp_function ([], _, Pfunction_cases (cases, _, _)) ->
132163
let cases, binding = Transformations.transform_cases ~loc cases in
133-
[%expr fun _ppx_mikmatch_v -> [%e cases]], binding @ acc
164+
[%expr fun _ppx_mikmatch_v -> [%e cases]], List.map (fun b -> TopLevel, b) binding @ acc
134165
| Pexp_match (e, cases) ->
135166
let cases, binding = Transformations.transform_cases ~loc cases in
136167
( [%expr
137168
let _ppx_mikmatch_v = [%e e] in
138169
[%e cases]],
139-
binding @ acc )
170+
List.map (fun b -> TopLevel, b) binding @ acc )
140171
| Pexp_let (rec_flag, vbs, body) ->
141172
let processed_vbs, new_bindings =
142173
List.fold_left
143174
(fun (vbs_acc, bindings_acc) vb ->
144175
match vb.pvb_pat.ppat_desc, vb.pvb_expr.pexp_desc with
145176
| Ppat_constant (Pconst_string (pattern_str, _, _)), _ ->
146177
let new_vb, new_bindings = Transformations.transform_destructuring_let ~loc:vb.pvb_loc pattern_str vb.pvb_expr in
147-
new_vb :: vbs_acc, new_bindings @ bindings_acc
178+
new_vb :: vbs_acc, List.map (fun b -> TopLevel, b) new_bindings @ bindings_acc
148179
| _ ->
149180
Util.error ~loc
150181
"[%%pcre] and [%%mikmatch] only apply to match, function, global let declarations of strings, and let destructuring.")
@@ -157,9 +188,13 @@ let transformation =
157188
end
158189
(* match smth with | {%mikmatch|some regex|} -> ...*)
159190
| Pexp_match (matched_expr, cases) when has_ext_case cases ->
160-
Transformations.transform_mixed_match ~loc:e_ext.pexp_loc ~matched_expr cases acc
191+
let plain_acc = List.map snd acc in
192+
let expr, bindings = Transformations.transform_mixed_match ~loc:e_ext.pexp_loc ~matched_expr cases plain_acc in
193+
expr, List.map (fun b -> TopLevel, b) bindings
161194
| Pexp_function (params, constraint_, Pfunction_cases (cases, _, _)) when has_ext_case cases ->
162-
let transformed, acc = Transformations.transform_mixed_match ~loc:e_ext.pexp_loc cases acc in
195+
let plain_acc = List.map snd acc in
196+
let transformed, bindings = Transformations.transform_mixed_match ~loc:e_ext.pexp_loc cases plain_acc in
197+
let acc = List.map (fun b -> TopLevel, b) bindings in
163198
begin match params with
164199
| [] -> transformed, acc
165200
| _ -> { e_ext with pexp_desc = Pexp_function (params, constraint_, Pfunction_body transformed) }, acc
@@ -187,14 +222,89 @@ let impl str =
187222
match rev_bindings with
188223
| [] -> str
189224
| _ -> begin
190-
let loc = match List.hd (List.rev rev_bindings) with { pvb_loc; _ } -> pvb_loc in
191-
let struct_items =
192-
rev_bindings
193-
|> List.rev
194-
|> List.fold_left
195-
(fun acc binding -> acc @ [%str let[@warning "-32"] [%p binding.pvb_pat] = [%e binding.pvb_expr]])
196-
[%str [%%i pstr_value ~loc Nonrecursive [ dispatch_function_binding ~loc ]]]
225+
let loc = match List.hd (List.rev rev_bindings) with _, { pvb_loc; _ } -> pvb_loc in
226+
227+
let bindings = List.rev rev_bindings in
228+
229+
(* process all bindings in source order, build modules as needed *)
230+
let rec emit_in_order remaining =
231+
match remaining with
232+
| [] -> []
233+
| (TopLevel, vb) :: rest ->
234+
(* emit top-level binding immediately *)
235+
let items = [%str let[@warning "-32"] [%p vb.pvb_pat] = [%e vb.pvb_expr]] in
236+
items @ emit_in_order rest
237+
| (InModule path, vb) :: rest ->
238+
let root = List.hd path in
239+
(* collect all consecutive bindings for this root module *)
240+
let same_root, different =
241+
let rec collect acc = function
242+
| ((InModule p, _) as b) :: rest when List.hd p = root -> collect (b :: acc) rest
243+
| rest -> List.rev acc, rest
244+
in
245+
collect [ InModule path, vb ] rest
246+
in
247+
let mod_items = build_module_tree ~loc root same_root in
248+
mod_items @ emit_in_order different
249+
and build_module_tree ~loc root module_bindings =
250+
let by_path = Hashtbl.create 16 in
251+
List.iter
252+
begin fun binding ->
253+
match binding with
254+
| InModule path, vb ->
255+
let existing = try Hashtbl.find by_path path with Not_found -> [] in
256+
Hashtbl.replace by_path path (vb :: existing)
257+
| TopLevel, _ -> assert false
258+
end
259+
module_bindings;
260+
261+
let rec build_at_path current_path =
262+
let direct_bindings = try Hashtbl.find by_path current_path |> List.rev with Not_found -> [] in
263+
let direct_items = List.concat_map (fun vb -> [%str let[@warning "-32"] [%p vb.pvb_pat] = [%e vb.pvb_expr]]) direct_bindings in
264+
265+
let child_modules =
266+
Hashtbl.fold
267+
begin fun path _ acc ->
268+
if
269+
List.length path = List.length current_path + 1
270+
&& List.for_all2 ( = ) current_path (list_take (List.length current_path) path)
271+
then List.nth path (List.length current_path) :: acc
272+
else acc
273+
end
274+
by_path []
275+
|> List.sort_uniq compare
276+
in
277+
278+
(* build nested modules *)
279+
let nested_items =
280+
List.concat_map
281+
(fun child_name ->
282+
let child_path = current_path @ [ child_name ] in
283+
let child_items = build_at_path child_path in
284+
[
285+
pstr_module ~loc
286+
{
287+
pmb_name = { txt = Some child_name; loc };
288+
pmb_expr = pmod_structure ~loc child_items;
289+
pmb_attributes = [];
290+
pmb_loc = loc;
291+
};
292+
])
293+
child_modules
294+
in
295+
296+
direct_items @ nested_items
297+
in
298+
299+
let mod_body = build_at_path [ root ] in
300+
[
301+
pstr_module ~loc
302+
{ pmb_name = { txt = Some root; loc }; pmb_expr = pmod_structure ~loc mod_body; pmb_attributes = []; pmb_loc = loc };
303+
]
197304
in
305+
306+
let struct_items = [%str [%%i pstr_value ~loc Nonrecursive [ dispatch_function_binding ~loc ]]] @ emit_in_order bindings in
307+
198308
let mod_expr = pmod_structure ~loc struct_items in
199309
[%str open [%m mod_expr]] @ str
200310
end

0 commit comments

Comments
 (0)