Skip to content

Commit d75e9df

Browse files
committed
let destructuring via let%mikmatch/%pcre (#4)
1 parent 83eeeeb commit d75e9df

File tree

2 files changed

+123
-47
lines changed

2 files changed

+123
-47
lines changed

ppx_regexp/ppx_regexp.ml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,27 @@ let transformation ctx =
2626
(* let%mik/%pcre x = {|some regex|}*)
2727
| Pstr_extension (({ txt = ("pcre" | "mikmatch") as ext; _ }, PStr [ { pstr_desc = Pstr_value (rec_flag, vbs); _ } ]), _) ->
2828
let mode = if ext = "pcre" then `Pcre else `Mik in
29-
let bindings = Transformations.transform_let ~mode ~ctx vbs in
30-
let new_item = { item with pstr_desc = Pstr_value (rec_flag, bindings) } in
31-
new_item, acc
29+
let processed_vbs, collected_bindings =
30+
List.fold_left
31+
(fun (vbs_acc, bindings_acc) vb ->
32+
match vb.pvb_pat.ppat_desc, vb.pvb_expr.pexp_desc with
33+
(* pattern definition - let%mikmatch/%pcre name = {|/regex/|} *)
34+
| Ppat_var { txt = _; _ }, Pexp_constant (Pconst_string (value, _, _)) when not (String.length value > 0 && value.[0] = '/')
35+
->
36+
let transformed = Transformations.transform_let ~mode ~ctx [ vb ] in
37+
List.hd transformed :: vbs_acc, bindings_acc
38+
(* destructuring - let%mikmatch {|/pattern/|} = expr *)
39+
| Ppat_constant (Pconst_string (pattern_str, _, _)), _ ->
40+
let new_vb, new_bindings = Transformations.transform_destructuring_let ~mode ~ctx ~loc:vb.pvb_loc pattern_str vb.pvb_expr in
41+
new_vb :: vbs_acc, new_bindings @ bindings_acc
42+
| _ ->
43+
let transformed = Transformations.transform_let ~mode ~ctx [ vb ] in
44+
List.hd transformed :: vbs_acc, bindings_acc)
45+
([], acc) vbs
46+
in
47+
48+
let new_item = { item with pstr_desc = Pstr_value (rec_flag, List.rev processed_vbs) } in
49+
new_item, collected_bindings
3250
(* let x = expression (which might contain %mik/%pcre) *)
3351
| Pstr_value (rec_flag, vbs) ->
3452
let processed_vbs, collected_bindings =

ppx_regexp/transformations.ml

Lines changed: 102 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ module Regexp = struct
157157
end
158158

159159
module Parser = struct
160-
let get_parser mode target = match mode with `Pcre -> Regexp.parse_exn ~target | `Mik -> Regexp.parse_mik_exn ~target
160+
let get_parser ~mode ~target = match mode with `Pcre -> Regexp.parse_exn ~target | `Mik -> Regexp.parse_mik_exn ~target
161161

162162
let run ~parser ~ctx s =
163163
let r, flags = parser s in
@@ -167,41 +167,57 @@ module Parser = struct
167167
re, bs, nG, flags
168168
end
169169

170-
let make_default_rhs ~mode ~loc = function
170+
let make_default_rhs ~mode ~target ~loc = function
171171
| [] ->
172172
let open Lexing in
173173
let pos = loc.Location.loc_start in
174174
let pos_end = loc.Location.loc_end in
175-
let lnum = eint ~loc pos.pos_lnum in
176-
let lnum_end = eint ~loc pos_end.pos_lnum in
177-
let e0 = estring ~loc pos.pos_fname in
178-
let e2 = eint ~loc (pos.pos_cnum - pos.pos_bol) in
179-
let e2_start = eint ~loc (pos.pos_cnum - pos.pos_bol) in
180-
let e2_end = eint ~loc (pos_end.pos_cnum - pos_end.pos_bol) in
181-
begin
182-
match mode with
183-
| `Pcre ->
184-
let e = [%expr raise (Match_failure ([%e e0], [%e lnum], [%e e2]))] in
185-
Util.warn ~loc "A universal case is recommended for %%pcre." e
186-
| `Mik ->
187-
let str = [%expr Printf.sprintf "File %s, lines %d-%d, characters %d-%d: String did not match any of the mikmatch regexes."] in
188-
[%expr raise (Failure ([%e str] [%e e0] [%e lnum] [%e lnum_end] [%e e2_start] [%e e2_end]))]
175+
176+
(* pcre match uses Match_failure for compatibility *)
177+
if target = `Match && mode = `Pcre then begin
178+
let e =
179+
[%expr
180+
raise (Match_failure ([%e estring ~loc pos.pos_fname], [%e eint ~loc pos.pos_lnum], [%e eint ~loc (pos.pos_cnum - pos.pos_bol)]))]
181+
in
182+
Util.warn ~loc "A universal case is recommended for %%pcre." e
183+
end
184+
else begin
185+
(* all other cases use descriptive Failure *)
186+
let context =
187+
match target, mode with
188+
| `Match, `Pcre -> "any pcre cases"
189+
| `Match, `Mik -> "any mikmatch cases"
190+
| `Let, `Pcre -> "the pcre regex"
191+
| `Let, `Mik -> "the mikmatch regex"
192+
in
193+
194+
let location_desc =
195+
let char_start = pos.pos_cnum - pos.pos_bol in
196+
let char_end = pos_end.pos_cnum - pos_end.pos_bol in
197+
if pos.pos_lnum = pos_end.pos_lnum then Printf.sprintf "line %d, characters %d-%d" pos.pos_lnum char_start char_end
198+
else Printf.sprintf "lines %d-%d, characters %d-%d" pos.pos_lnum pos_end.pos_lnum char_start char_end
199+
in
200+
201+
let err_msg = Printf.sprintf "File %s, %s: String did not match %s." pos.pos_fname location_desc context in
202+
[%expr raise (Failure [%e estring ~loc err_msg])]
189203
end
190204
| default_cases ->
191205
let transformed =
192206
List.map
193-
(fun case ->
194-
match case.pc_lhs.ppat_desc with
195-
| Ppat_var var ->
196-
{
197-
case with
198-
pc_lhs = ppat_any ~loc;
199-
pc_rhs =
200-
[%expr
201-
let [%p ppat_var ~loc var] = _ppx_regexp_v in
202-
[%e case.pc_rhs]];
203-
}
204-
| _ -> case)
207+
begin
208+
fun case ->
209+
match case.pc_lhs.ppat_desc with
210+
| Ppat_var var ->
211+
{
212+
case with
213+
pc_lhs = ppat_any ~loc;
214+
pc_rhs =
215+
[%expr
216+
let [%p ppat_var ~loc var] = _ppx_regexp_v in
217+
[%e case.pc_rhs]];
218+
}
219+
| _ -> case
220+
end
205221
default_cases
206222
in
207223
begin
@@ -216,7 +232,7 @@ let build_exec_match ~loc ~re_var ~continue_next ~on_match =
216232
(* Transformations *)
217233

218234
let transform_let ~mode ~ctx =
219-
let parser = Parser.get_parser mode `Let in
235+
let parser = Parser.get_parser ~mode ~target:`Let in
220236
List.map
221237
begin
222238
fun vb ->
@@ -238,6 +254,42 @@ let transform_let ~mode ~ctx =
238254
| _ -> vb
239255
end
240256

257+
let transform_destructuring_let ~mode ~ctx ~loc pattern_str expr =
258+
let pos = loc.loc_start in
259+
let parser = Parser.get_parser ~mode ~target:`Match ~pos in
260+
let re, bs, _, flags = Parser.run ~parser ~ctx pattern_str in
261+
let capture_names = List.map (fun (name, _, _, _) -> name) (List.rev bs) in
262+
263+
let lhs_pattern =
264+
match capture_names with
265+
| [] -> [%pat? ()]
266+
| [ name ] -> ppat_var ~loc name
267+
| names -> ppat_tuple ~loc (List.map (fun n -> ppat_var ~loc n) names)
268+
in
269+
270+
let re_var = Util.fresh_var () in
271+
let re_binding = Re_comp.compile ~loc re_var [ re, flags ] in
272+
273+
let on_match =
274+
match capture_names with
275+
| [] -> [%expr ()]
276+
| [ _ ] -> [%expr Re.Group.get _g 1]
277+
| names ->
278+
let exprs = List.mapi (fun i _ -> [%expr Re.Group.get _g [%e eint ~loc (i + 1)]]) names in
279+
pexp_tuple ~loc exprs
280+
in
281+
282+
let default_rhs = [%expr [%e make_default_rhs ~mode ~target:`Let ~loc []]] in
283+
284+
let re_var = pexp_ident ~loc { txt = Lident re_var; loc } in
285+
let rhs_expr =
286+
[%expr
287+
let _ppx_regexp_v = [%e expr] in
288+
[%e build_exec_match ~loc ~re_var ~continue_next:default_rhs ~on_match]]
289+
in
290+
291+
{ pvb_pat = lhs_pattern; pvb_expr = rhs_expr; pvb_attributes = []; pvb_loc = loc }, [ re_binding ]
292+
241293
let transform_cases ~mode ~loc ~ctx cases =
242294
let partition_cases cases =
243295
let rec partition pattern_cases = function
@@ -252,7 +304,7 @@ let transform_cases ~mode ~loc ~ctx cases =
252304
Ast_pattern.(parse (pstring __')) case.pc_lhs.ppat_loc case.pc_lhs (fun { txt = re_src; loc = { loc_start; loc_end; _ } } ->
253305
let re_offset = (loc_end.pos_cnum - loc_start.pos_cnum - String.length re_src) / 2 in
254306
let pos = { loc_start with pos_cnum = loc_start.pos_cnum + re_offset; pos_lnum = loc_end.pos_lnum } in
255-
let parser = Parser.get_parser mode `Match ~pos in
307+
let parser = Parser.get_parser ~mode ~target:`Match ~pos in
256308
let re, bs, nG, flags = Parser.run ~parser ~ctx re_src in
257309
let re_str = Pprintast.string_of_expression re in
258310
re, re_str, nG, bs, case.pc_rhs, case.pc_guard, flags)
@@ -289,7 +341,7 @@ let transform_cases ~mode ~loc ~ctx cases =
289341
| Some (re_data, existing) -> (key, (re_data, handlers :: existing)) :: List.remove_assoc key patterns
290342
| None -> (key, (re_data, [ handlers ])) :: patterns
291343
in
292-
List.fold_left add_case [] cases |> List.map (fun ((_re_str, _flags), (re_data, handlers)) -> re_data, List.rev handlers) |> List.rev
344+
List.fold_left add_case [] cases |> List.map (fun ((_, _), (re_data, handlers)) -> re_data, List.rev handlers) |> List.rev
293345
in
294346

295347
let add_offsets patterns =
@@ -330,9 +382,11 @@ let transform_cases ~mode ~loc ~ctx cases =
330382
in
331383

332384
let handler_bindings =
333-
processed_groups
334-
|> List.concat_map (fun (_, _, handlers) ->
335-
handlers |> List.map (fun (name, body) -> value_binding ~loc ~pat:(ppat_var ~loc { txt = name; loc }) ~expr:body))
385+
List.concat_map
386+
begin
387+
fun (_, _, handlers) -> List.map (fun (name, expr) -> value_binding ~loc ~pat:(ppat_var ~loc { txt = name; loc }) ~expr) handlers
388+
end
389+
processed_groups
336390
in
337391

338392
let build_match_cascade () =
@@ -343,14 +397,16 @@ let transform_cases ~mode ~loc ~ctx cases =
343397
let is_single = match patterns with [ _ ] -> true | _ -> false in
344398

345399
let on_match =
346-
if is_single then (
400+
if is_single then begin
347401
let handler = pexp_ident ~loc { txt = Lident (fst (List.hd handlers)); loc } in
348-
[%expr match [%e handler] _g with Some result -> result | None -> [%e continue]])
349-
else (
402+
[%expr match [%e handler] _g with Some result -> result | None -> [%e continue]]
403+
end
404+
else begin
350405
let handler_array = handlers |> List.map (fun (name, _) -> pexp_ident ~loc { txt = Lident name; loc }) |> pexp_array ~loc in
351406
let dispatch = [%expr __ppx_regexp_dispatch (snd [%e re_var]) [%e handler_array] _g] in
352407
if has_guards then [%expr match [%e dispatch] with Some result -> result | None -> [%e continue]]
353-
else [%expr match [%e dispatch] with Some result -> result | None -> assert false])
408+
else [%expr match [%e dispatch] with Some result -> result | None -> assert false]
409+
end
354410
in
355411

356412
case
@@ -372,11 +428,13 @@ let transform_cases ~mode ~loc ~ctx cases =
372428
in
373429

374430
let pattern_cases, default_cases = partition_cases cases in
375-
let default_rhs = make_default_rhs ~mode ~loc default_cases in
376-
377-
pattern_cases |> List.map (parse_pattern ~mode ~ctx) |> create_compilation_groups |> fun groups ->
378-
let processed = List.mapi process_compilation_group groups in
379-
generate_code groups processed default_rhs
431+
let default_rhs = make_default_rhs ~mode ~target:`Match ~loc default_cases in
432+
if pattern_cases = [] then default_rhs, [] (* no patterns, no need for match cascading *)
433+
else begin
434+
pattern_cases |> List.map (parse_pattern ~mode ~ctx) |> create_compilation_groups |> fun groups ->
435+
let processed = List.mapi process_compilation_group groups in
436+
generate_code groups processed default_rhs
437+
end
380438

381439
let transform_mixed_match ~loc ~ctx ?matched_expr cases acc =
382440
let aux case =
@@ -386,7 +444,7 @@ let transform_mixed_match ~loc ~ctx ?matched_expr cases acc =
386444
PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_constant (Pconst_string (pat, str_loc, _)); _ }, _); _ } ] ) ->
387445
let pos = str_loc.loc_start in
388446
let mode = if "pcre" = ext then `Pcre else `Mik in
389-
let parser = Parser.get_parser mode `Match ~pos in
447+
let parser = Parser.get_parser ~mode ~target:`Match ~pos in
390448
let re, bs, nG, flags = Parser.run ~parser ~ctx pat in
391449
`Ext (re, nG, bs, case.pc_rhs, case.pc_guard, flags)
392450
| _ -> `Regular case

0 commit comments

Comments
 (0)