Skip to content

Commit 30d472a

Browse files
committed
feature: pattern guard support for ppx_regexp
1 parent 18ca331 commit 30d472a

File tree

2 files changed

+151
-54
lines changed

2 files changed

+151
-54
lines changed

ppx_regexp.opam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ bug-reports: "https://github.com/paurkedal/ppx_regexp/issues"
1010
depends: [
1111
"ocaml" {>= "4.02.3"}
1212
"dune" {>= "1.11"}
13-
"ppxlib" {>= "0.9.0"}
13+
"ppxlib" {>= "0.9.0" & <= "0.35.0"}
1414
"re" {>= "1.7.2"}
1515
"qcheck" {with-test}
1616
]

ppx_regexp/ppx_regexp.ml

Lines changed: 150 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -137,69 +137,166 @@ let rec wrap_group_bindings ~loc rhs offG = function
137137
let [%p ppat_var ~loc varG] = [%e eG] in
138138
[%e wrap_group_bindings ~loc rhs offG bs]]
139139

140+
let rec separate_defaults acc = function
141+
| [] -> List.rev acc, []
142+
| ({ pc_lhs = { ppat_desc = Ppat_any; _ }; _ } as case) :: rest -> acc, case :: rest
143+
| ({ pc_lhs = { ppat_desc = Ppat_var _; _ }; _ } as case) :: rest -> acc, case :: rest
144+
| case :: rest -> separate_defaults (case :: acc) rest
145+
146+
let make_default_rhs ~loc = function
147+
| [] ->
148+
let open Lexing in
149+
let pos = loc.Location.loc_start in
150+
let e0 = estring ~loc pos.pos_fname in
151+
let e1 = eint ~loc pos.pos_lnum in
152+
let e2 = eint ~loc (pos.pos_cnum - pos.pos_bol) in
153+
let e = [%expr raise (Match_failure ([%e e0], [%e e1], [%e e2]))] in
154+
warn ~loc "A universal case is recommended." e
155+
| default_cases ->
156+
let transformed =
157+
List.map
158+
(fun case ->
159+
match case.pc_lhs.ppat_desc with
160+
| Ppat_var var ->
161+
{
162+
case with
163+
pc_lhs = ppat_any ~loc;
164+
pc_rhs =
165+
[%expr
166+
let [%p ppat_var ~loc var] = _ppx_regexp_v in
167+
[%e case.pc_rhs]];
168+
}
169+
| _ -> case)
170+
default_cases
171+
in
172+
match transformed with
173+
| [{ pc_lhs = { ppat_desc = Ppat_any; _ }; pc_guard = None; pc_rhs; _ }] ->
174+
pc_rhs
175+
| _ ->
176+
pexp_match ~loc [%expr _ppx_regexp_v] transformed
177+
140178
let transform_cases ~loc cases =
141179
let aux case =
142-
if case.pc_guard <> None then
143-
error ~loc "Guards are not implemented for match%%pcre."
144-
else
145-
Ast_pattern.(parse (pstring __')) loc case.pc_lhs
146-
begin fun {txt = re_src; loc = {loc_start; loc_end; _}} ->
147-
let re_offset =
148-
(loc_end.pos_cnum - loc_start.pos_cnum - String.length re_src) / 2
149-
in
150-
let pos = {loc_start with pos_cnum = loc_start.pos_cnum + re_offset} in
151-
let re, bs, nG = extract_bindings ~pos re_src in
152-
(re, nG, bs, case.pc_rhs)
180+
Ast_pattern.(parse (pstring __'))
181+
loc case.pc_lhs
182+
begin
183+
fun { txt = re_src; loc = { loc_start; loc_end; _ } } ->
184+
let re_offset = (loc_end.pos_cnum - loc_start.pos_cnum - String.length re_src) / 2 in
185+
let pos = { loc_start with pos_cnum = loc_start.pos_cnum + re_offset; pos_lnum = loc_end.pos_lnum } in
186+
let re, bs, nG = extract_bindings ~pos re_src in
187+
re, nG, bs, case.pc_rhs, case.pc_guard
188+
end
189+
in
190+
let group_by_pattern cases =
191+
List.fold_left
192+
begin
193+
fun acc (re, nG, bs, rhs, guard) ->
194+
let found, groups =
195+
List.fold_left
196+
begin
197+
fun (found, acc_groups) (re', cases) ->
198+
if found then found, (re', cases) :: acc_groups
199+
else if re = re' then true, (re', (nG, bs, rhs, guard) :: cases) :: acc_groups
200+
else false, (re', cases) :: acc_groups
201+
end
202+
(false, []) acc
203+
in
204+
if found then groups else (re, [ nG, bs, rhs, guard ]) :: groups
153205
end
206+
[] cases
154207
in
155-
let cases, default_rhs =
156-
(match List.rev (*_map rewrite_case*) cases with
157-
| {pc_lhs = {ppat_desc = Ppat_any; _}; pc_rhs; pc_guard = None} :: cases ->
158-
(cases, pc_rhs)
159-
| {pc_lhs = {ppat_desc = Ppat_var var; _}; pc_rhs; pc_guard = None} ::
160-
cases ->
161-
let rhs =
162-
[%expr let [%p ppat_var ~loc var] = _ppx_regexp_v in [%e pc_rhs]] in
163-
(cases, rhs)
164-
| cases ->
165-
let open Lexing in
166-
let pos = loc.Location.loc_start in
167-
let e0 = estring ~loc pos.pos_fname in
168-
let e1 = eint ~loc pos.pos_lnum in
169-
let e2 = eint ~loc (pos.pos_cnum - pos.pos_bol) in
170-
let e = [%expr raise (Match_failure ([%e e0], [%e e1], [%e e2]))] in
171-
(cases, warn ~loc "A universal case is recommended for %pcre." e))
208+
209+
let compute_offsets l =
210+
let result, _ =
211+
List.fold_left
212+
begin
213+
fun (acc, offG) (re, case_group) ->
214+
let nG =
215+
let n, _, _, _ = List.hd (List.rev case_group) in
216+
n
217+
in
218+
(re, case_group, offG) :: acc, offG + nG
219+
end
220+
([], 0) l
221+
in
222+
List.rev result
172223
in
173-
let cases = List.rev_map aux cases in
174-
let res = pexp_array ~loc (List.map (fun (re, _, _, _) -> re) cases) in
175-
let comp = [%expr
176-
let a = Array.map (fun s -> Re.mark (Re.Perl.re s)) [%e res] in
177-
let marks = Array.map fst a in
178-
let re = Re.compile (Re.alt (Array.to_list (Array.map snd a))) in
179-
(re, marks)
180-
] in
224+
225+
let cases, default_cases = separate_defaults [] cases in
226+
let default_rhs = make_default_rhs ~loc default_cases in
227+
let processed_cases = List.map aux cases |> group_by_pattern |> compute_offsets in
228+
229+
let res = pexp_array ~loc @@ List.map (fun (re, _, _) -> re) processed_cases in
230+
231+
let comp =
232+
[%expr
233+
let a = Array.map (fun s -> Re.mark (Re.Perl.re s)) [%e res] in
234+
let marks = Array.map fst a in
235+
let re = Re.compile (Re.alt (Array.to_list (Array.map snd a))) in
236+
re, marks]
237+
in
238+
181239
let var = fresh_var () in
182-
let re_binding =
183-
value_binding ~loc ~pat:(ppat_var ~loc {txt = var; loc}) ~expr:comp
240+
let re_binding = value_binding ~loc ~pat:(ppat_var ~loc { txt = var; loc }) ~expr:comp in
241+
let e_comp = pexp_ident ~loc { txt = Lident var; loc } in
242+
243+
let case_handlers =
244+
List.mapi
245+
begin
246+
fun i (_, case_group, offG) ->
247+
let handler_name = Printf.sprintf "_case_%d" i in
248+
let handler_body =
249+
let rec mk_guard_chains = function
250+
| [] -> [%expr None]
251+
| (_, bs, rhs, guard) :: rest ->
252+
let bs = List.rev bs in
253+
begin
254+
match guard with
255+
| None -> [%expr Some [%e wrap_group_bindings ~loc rhs offG bs]]
256+
| Some guard_expr ->
257+
let guarded = [%expr if [%e guard_expr] then Some [%e rhs] else [%e mk_guard_chains rest]] in
258+
wrap_group_bindings ~loc guarded offG bs
259+
end
260+
in
261+
[%expr fun _g -> [%e mk_guard_chains (List.rev case_group)]]
262+
in
263+
handler_name, handler_body
264+
end
184265
in
185-
let e_comp = pexp_ident ~loc {txt = Lident var; loc} in
186-
187-
let rec handle_cases i offG = function
188-
| [] -> [%expr assert false]
189-
| (_, nG, bs, rhs) :: cases ->
190-
[%expr
191-
if Re.Mark.test _g (snd [%e e_comp]).([%e eint ~loc i]) then
192-
[%e wrap_group_bindings ~loc rhs offG bs]
193-
else
194-
[%e handle_cases (i + 1) (offG + nG) cases]]
266+
267+
let mk_checks cases_with_offsets =
268+
let indexed = List.mapi (fun i x -> i, x) cases_with_offsets in
269+
List.fold_right
270+
begin
271+
fun (i, _) acc ->
272+
let handler_name = Printf.sprintf "_case_%d" i in
273+
[%expr
274+
if Re.Mark.test _g (snd [%e e_comp]).([%e eint ~loc i]) then [%e pexp_ident ~loc { txt = Lident handler_name; loc }] _g
275+
else [%e acc]]
276+
end
277+
indexed [%expr None]
195278
in
196-
let cases =
279+
280+
let handlers = case_handlers processed_cases in
281+
let dispatchers = mk_checks processed_cases in
282+
283+
let match_expr =
197284
[%expr
198-
(match Re.exec_opt (fst [%e e_comp]) _ppx_regexp_v with
199-
| None -> [%e default_rhs]
200-
| Some _g -> [%e handle_cases 0 0 cases])]
285+
match Re.exec_opt (fst [%e e_comp]) _ppx_regexp_v with
286+
| None -> [%e default_rhs]
287+
| Some _g ->
288+
[%e
289+
List.fold_left
290+
begin
291+
fun acc (name, body) ->
292+
[%expr
293+
let [%p ppat_var ~loc { txt = name; loc }] = [%e body] in
294+
[%e acc]]
295+
end
296+
[%expr match [%e dispatchers] with Some result -> result | None -> [%e default_rhs]]
297+
handlers]]
201298
in
202-
(cases, re_binding)
299+
match_expr, re_binding
203300

204301
let transformation = object
205302
inherit [value_binding list] Ast_traverse.fold_map as super

0 commit comments

Comments
 (0)