1717open Ppxlib
1818open 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+
2025let 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
3234let 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