This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,673 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
type sem = [ `Longest | `Shortest | `First ]
type rep_kind = [ `Greedy | `Non_greedy ]
type mark = int
type idx = int
type expr = { id : int; def : def }
and def =
Cst of Cset.t
| Alt of expr list
| Seq of sem * expr * expr
| Eps
| Rep of rep_kind * sem * expr
| Mark of int
| Erase of int * int
| Before of Category.t
| After of Category.t
| Pmark of Pmark.t
let hash_combine h accu = accu * 65599 + h
module Marks = struct
type t =
{ marks : (int * int) list
; pmarks : Pmark.Set.t }
let empty = { marks = [] ; pmarks = Pmark.Set.empty }
let rec merge_marks_offset old = function
| [] ->
old
| (i, v) :: rem ->
let nw' = merge_marks_offset (List.remove_assq i old) rem in
if v = -2 then
nw'
else
(i, v) :: nw'
let merge old nw =
{ marks = merge_marks_offset old.marks nw.marks
; pmarks = Pmark.Set.union old.pmarks nw.pmarks }
let rec hash_marks_offset l accu =
match l with
[] -> accu
| (a, i) :: r -> hash_marks_offset r (hash_combine a (hash_combine i accu))
let hash m accu =
hash_marks_offset m.marks (hash_combine (Hashtbl.hash m.pmarks) accu)
let rec marks_set_idx idx = function
| (a, -1) :: rem ->
(a, idx) :: marks_set_idx idx rem
| marks ->
marks
let marks_set_idx marks idx =
{ marks with marks = marks_set_idx idx marks.marks }
let pp_marks ch t =
match t.marks with
| [] ->
()
| (a, i) :: r ->
Format.fprintf ch "%d-%d" a i;
List.iter (fun (a, i) -> Format.fprintf ch " %d-%d" a i) r
end
(****)
let pp_sem ch k =
Format.pp_print_string ch
(match k with
`Shortest -> "short"
| `Longest -> "long"
| `First -> "first")
let pp_rep_kind fmt = function
| `Greedy -> Format.pp_print_string fmt "Greedy"
| `Non_greedy -> Format.pp_print_string fmt "Non_greedy"
let rec pp ch e =
let open Fmt in
match e.def with
Cst l ->
sexp ch "cst" Cset.pp l;
| Alt l ->
sexp ch "alt" (list pp) l
| Seq (k, e, e') ->
sexp ch "seq" (triple pp_sem pp pp) (k, e, e')
| Eps ->
str ch "eps"
| Rep (_rk, k, e) ->
sexp ch "rep" (pair pp_sem pp) (k, e)
| Mark i ->
sexp ch "mark" int i
| Pmark i ->
sexp ch "pmark" int (i :> int)
| Erase (b, e) ->
sexp ch "erase" (pair int int) (b, e)
| Before c ->
sexp ch "before" Category.pp c
| After c ->
sexp ch "after" Category.pp c
(****)
let rec first f = function
| [] ->
None
| x :: r ->
match f x with
None -> first f r
| Some _ as res -> res
(****)
type ids = int ref
let create_ids () = ref 0
let eps_expr = { id = 0; def = Eps }
let mk_expr ids def =
incr ids;
{ id = !ids; def = def }
let empty ids = mk_expr ids (Alt [])
let cst ids s =
if Cset.is_empty s
then empty ids
else mk_expr ids (Cst s)
let alt ids = function
| [] -> empty ids
| [c] -> c
| l -> mk_expr ids (Alt l)
let seq ids kind x y =
match x.def, y.def with
Alt [], _ -> x
| _, Alt [] -> y
| Eps, _ -> y
| _, Eps when kind = `First -> x
| _ -> mk_expr ids (Seq (kind, x, y))
let is_eps expr =
match expr.def with
| Eps -> true
| _ -> false
let eps ids = mk_expr ids Eps
let rep ids kind sem x = mk_expr ids (Rep (kind, sem, x))
let mark ids m = mk_expr ids (Mark m)
let pmark ids i = mk_expr ids (Pmark i)
let erase ids m m' = mk_expr ids (Erase (m, m'))
let before ids c = mk_expr ids (Before c)
let after ids c = mk_expr ids (After c)
(****)
let rec rename ids x =
match x.def with
Cst _ | Eps | Mark _ | Pmark _ | Erase _ | Before _ | After _ ->
mk_expr ids x.def
| Alt l ->
mk_expr ids (Alt (List.map (rename ids) l))
| Seq (k, y, z) ->
mk_expr ids (Seq (k, rename ids y, rename ids z))
| Rep (g, k, y) ->
mk_expr ids (Rep (g, k, rename ids y))
(****)
type hash = int
type mark_infos = int array
type status = Failed | Match of mark_infos * Pmark.Set.t | Running
module E = struct
type t =
| TSeq of t list * expr * sem
| TExp of Marks.t * expr
| TMatch of Marks.t
let rec equal l1 l2 =
match l1, l2 with
| [], [] ->
true
| TSeq (l1', e1, _) :: r1, TSeq (l2', e2, _) :: r2 ->
e1.id = e2.id && equal l1' l2' && equal r1 r2
| TExp (marks1, e1) :: r1, TExp (marks2, e2) :: r2 ->
e1.id = e2.id && marks1 = marks2 && equal r1 r2
| TMatch marks1 :: r1, TMatch marks2 :: r2 ->
marks1 = marks2 && equal r1 r2
| _ ->
false
let rec hash l accu =
match l with
| [] ->
accu
| TSeq (l', e, _) :: r ->
hash r (hash_combine 0x172a1bce (hash_combine e.id (hash l' accu)))
| TExp (marks, e) :: r ->
hash r
(hash_combine 0x2b4c0d77 (hash_combine e.id (Marks.hash marks accu)))
| TMatch marks :: r ->
hash r (hash_combine 0x1c205ad5 (Marks.hash marks accu))
let texp marks x = TExp (marks, x)
let tseq kind x y rem =
match x with
[] -> rem
| [TExp (marks, {def = Eps ; _})] -> TExp (marks, y) :: rem
| _ -> TSeq (x, y, kind) :: rem
let rec print_state_rec ch e y =
match e with
| TMatch marks ->
Format.fprintf ch "@[<2>(Match@ %a)@]" Marks.pp_marks marks
| TSeq (l', x, _kind) ->
Format.fprintf ch "@[<2>(Seq@ ";
print_state_lst ch l' x;
Format.fprintf ch "@ %a)@]" pp x
| TExp (marks, {def = Eps; _}) ->
Format.fprintf ch "@[<2>(Exp@ %d@ (%a)@ (eps))@]" y.id Marks.pp_marks marks
| TExp (marks, x) ->
Format.fprintf ch "@[<2>(Exp@ %d@ (%a)@ %a)@]" x.id Marks.pp_marks marks pp x
and print_state_lst ch l y =
match l with
[] ->
Format.fprintf ch "()"
| e :: rem ->
print_state_rec ch e y;
List.iter
(fun e ->
Format.fprintf ch "@ | ";
print_state_rec ch e y)
rem
let pp ch t = print_state_lst ch [t] { id = 0; def = Eps }
end
module State = struct
type t =
{ idx: idx
; category: Category.t
; desc: E.t list
; mutable status: status option
; hash: hash }
let dummy =
{ idx = -1
; category = Category.dummy
; desc = []
; status = None
; hash = -1 }
let hash idx cat desc =
E.hash desc (hash_combine idx (hash_combine (Category.to_int cat) 0)) land 0x3FFFFFFF
let mk idx cat desc =
{ idx
; category = cat
; desc
; status = None
; hash = hash idx cat desc}
let create cat e = mk 0 cat [E.TExp (Marks.empty, e)]
let equal x y =
(x.hash : int) = y.hash && (x.idx : int) = y.idx &&
Category.equal x.category y.category && E.equal x.desc y.desc
let compare x y =
let c = compare (x.hash : int) y.hash in
if c <> 0 then c else
let c = Category.compare x.category y.category in
if c <> 0 then c else
compare x.desc y.desc
type t' = t
module Table = Hashtbl.Make(
struct
type t = t'
let equal = equal
let hash t = t.hash
end)
end
(**** Find a free index ****)
type working_area = bool array ref
let create_working_area () = ref [| false |]
let index_count w = Array.length !w
let reset_table a = Array.fill a 0 (Array.length a) false
let rec mark_used_indices tbl =
List.iter (function
| E.TSeq (l, _, _) -> mark_used_indices tbl l
| E.TExp (marks, _)
| E.TMatch marks ->
List.iter (fun (_, i) -> if i >= 0 then tbl.(i) <- true)
marks.Marks.marks)
let rec find_free tbl idx len =
if idx = len || not tbl.(idx) then idx else find_free tbl (idx + 1) len
let free_index tbl_ref l =
let tbl = !tbl_ref in
reset_table tbl;
mark_used_indices tbl l;
let len = Array.length tbl in
let idx = find_free tbl 0 len in
if idx = len then tbl_ref := Array.make (2 * len) false;
idx
(**** Computation of the next state ****)
let remove_matches = List.filter (function E.TMatch _ -> false | _ -> true)
let rec split_at_match_rec l' = function
| [] -> assert false
| E.TMatch _ :: r -> (List.rev l', remove_matches r)
| x :: r -> split_at_match_rec (x :: l') r
let split_at_match l = split_at_match_rec [] l
let rec remove_duplicates prev l y =
match l with
[] ->
([], prev)
| E.TMatch _ as x :: _ -> (* Truncate after first match *)
([x], prev)
| E.TSeq (l', x, kind) :: r ->
let (l'', prev') = remove_duplicates prev l' x in
let (r', prev'') = remove_duplicates prev' r y in
(E.tseq kind l'' x r', prev'')
| E.TExp (_marks, {def = Eps; _}) as e :: r ->
if List.memq y.id prev then
remove_duplicates prev r y
else
let (r', prev') = remove_duplicates (y.id :: prev) r y in
(e :: r', prev')
| E.TExp (_marks, x) as e :: r ->
if List.memq x.id prev then
remove_duplicates prev r y
else
let (r', prev') = remove_duplicates (x.id :: prev) r y in
(e :: r', prev')
let rec set_idx idx = function
| [] ->
[]
| E.TMatch marks :: r ->
E.TMatch (Marks.marks_set_idx marks idx) :: set_idx idx r
| E.TSeq (l', x, kind) :: r ->
E.TSeq (set_idx idx l', x, kind) :: set_idx idx r
| E.TExp (marks, x) :: r ->
E.TExp ((Marks.marks_set_idx marks idx), x) :: set_idx idx r
let filter_marks b e marks =
{marks with Marks.marks = List.filter (fun (i, _) -> i < b || i > e) marks.Marks.marks }
let rec delta_1 marks c ~next_cat ~prev_cat x rem =
(*Format.eprintf "%d@." x.id;*)
match x.def with
Cst s ->
if Cset.mem c s then E.texp marks eps_expr :: rem else rem
| Alt l ->
delta_2 marks c ~next_cat ~prev_cat l rem
| Seq (kind, y, z) ->
let y' = delta_1 marks c ~next_cat ~prev_cat y [] in
delta_seq c ~next_cat ~prev_cat kind y' z rem
| Rep (rep_kind, kind, y) ->
let y' = delta_1 marks c ~next_cat ~prev_cat y [] in
let (y'', marks') =
match
first
(function E.TMatch marks -> Some marks | _ -> None) y'
with
None -> (y', marks)
| Some marks' -> (remove_matches y', marks')
in
begin match rep_kind with
`Greedy -> E.tseq kind y'' x (E.TMatch marks' :: rem)
| `Non_greedy -> E.TMatch marks :: E.tseq kind y'' x rem
end
| Eps ->
E.TMatch marks :: rem
| Mark i ->
let marks = { marks with Marks.marks = (i, -1) :: List.remove_assq i marks.Marks.marks } in
E.TMatch marks :: rem
| Pmark i ->
let marks = { marks with Marks.pmarks = Pmark.Set.add i marks.Marks.pmarks } in
E.TMatch marks :: rem
| Erase (b, e) ->
E.TMatch (filter_marks b e marks) :: rem
| Before cat'' ->
if Category.intersect next_cat cat'' then E.TMatch marks :: rem else rem
| After cat'' ->
if Category.intersect prev_cat cat'' then E.TMatch marks :: rem else rem
and delta_2 marks c ~next_cat ~prev_cat l rem =
match l with
[] -> rem
| y :: r ->
delta_1 marks c ~next_cat ~prev_cat y
(delta_2 marks c ~next_cat ~prev_cat r rem)
and delta_seq c ~next_cat ~prev_cat kind y z rem =
match
first (function E.TMatch marks -> Some marks | _ -> None) y
with
None ->
E.tseq kind y z rem
| Some marks ->
match kind with
`Longest ->
E.tseq kind (remove_matches y) z
(delta_1 marks c ~next_cat ~prev_cat z rem)
| `Shortest ->
delta_1 marks c ~next_cat ~prev_cat z
(E.tseq kind (remove_matches y) z rem)
| `First ->
let (y', y'') = split_at_match y in
E.tseq kind y' z
(delta_1 marks c ~next_cat ~prev_cat z (E.tseq kind y'' z rem))
let rec delta_3 c ~next_cat ~prev_cat x rem =
match x with
E.TSeq (y, z, kind) ->
let y' = delta_4 c ~next_cat ~prev_cat y [] in
delta_seq c ~next_cat ~prev_cat kind y' z rem
| E.TExp (marks, e) ->
delta_1 marks c ~next_cat ~prev_cat e rem
| E.TMatch _ ->
x :: rem
and delta_4 c ~next_cat ~prev_cat l rem =
match l with
[] -> rem
| y :: r ->
delta_3 c ~next_cat ~prev_cat y
(delta_4 c ~next_cat ~prev_cat r rem)
let delta tbl_ref next_cat char st =
let prev_cat = st.State.category in
let (expr', _) =
remove_duplicates []
(delta_4 char ~next_cat ~prev_cat st.State.desc [])
eps_expr in
let idx = free_index tbl_ref expr' in
let expr'' = set_idx idx expr' in
State.mk idx next_cat expr''
(****)
let rec red_tr = function
| [] | [_] as l ->
l
| ((s1, st1) as tr1) :: ((s2, st2) as tr2) :: rem ->
if State.equal st1 st2 then
red_tr ((Cset.union s1 s2, st1) :: rem)
else
tr1 :: red_tr (tr2 :: rem)
let simpl_tr l =
List.sort
(fun (s1, _) (s2, _) -> compare s1 s2)
(red_tr (List.sort (fun (_, st1) (_, st2) -> State.compare st1 st2) l))
(****)
let prepend_deriv = List.fold_right (fun (s, x) l -> Cset.prepend s x l)
let rec restrict s = function
| [] -> []
| (s', x') :: rem ->
let s'' = Cset.inter s s' in
if Cset.is_empty s''
then restrict s rem
else (s'', x') :: restrict s rem
let rec remove_marks b e rem =
if b > e then rem else remove_marks b (e - 1) ((e, -2) :: rem)
let rec prepend_marks_expr m = function
| E.TSeq (l, e', s) -> E.TSeq (prepend_marks_expr_lst m l, e', s)
| E.TExp (m', e') -> E.TExp (Marks.merge m m', e')
| E.TMatch m' -> E.TMatch (Marks.merge m m')
and prepend_marks_expr_lst m l =
List.map (prepend_marks_expr m) l
let prepend_marks m =
List.map (fun (s, x) -> (s, prepend_marks_expr_lst m x))
let rec deriv_1 all_chars categories marks cat x rem =
match x.def with
| Cst s ->
Cset.prepend s [E.texp marks eps_expr] rem
| Alt l ->
deriv_2 all_chars categories marks cat l rem
| Seq (kind, y, z) ->
let y' = deriv_1 all_chars categories marks cat y [(all_chars, [])] in
deriv_seq all_chars categories cat kind y' z rem
| Rep (rep_kind, kind, y) ->
let y' = deriv_1 all_chars categories marks cat y [(all_chars, [])] in
List.fold_right
(fun (s, z) rem ->
let (z', marks') =
match
first
(function E.TMatch marks -> Some marks | _ -> None)
z
with
None -> (z, marks)
| Some marks' -> (remove_matches z, marks')
in
Cset.prepend s
(match rep_kind with
`Greedy -> E.tseq kind z' x [E.TMatch marks']
| `Non_greedy -> E.TMatch marks :: E.tseq kind z' x [])
rem)
y' rem
| Eps ->
Cset.prepend all_chars [E.TMatch marks] rem
| Mark i ->
Cset.prepend all_chars [E.TMatch {marks with Marks.marks = ((i, -1) :: List.remove_assq i marks.Marks.marks)}] rem
| Pmark _ ->
Cset.prepend all_chars [E.TMatch marks] rem
| Erase (b, e) ->
Cset.prepend all_chars
[E.TMatch {marks with Marks.marks = (remove_marks b e (filter_marks b e marks).Marks.marks)}] rem
| Before cat' ->
Cset.prepend (List.assq cat' categories) [E.TMatch marks] rem
| After cat' ->
if Category.intersect cat cat' then Cset.prepend all_chars [E.TMatch marks] rem else rem
and deriv_2 all_chars categories marks cat l rem =
match l with
[] -> rem
| y :: r -> deriv_1 all_chars categories marks cat y
(deriv_2 all_chars categories marks cat r rem)
and deriv_seq all_chars categories cat kind y z rem =
if
List.exists
(fun (_s, xl) ->
List.exists (function E.TMatch _ -> true | _ -> false) xl)
y
then
let z' = deriv_1 all_chars categories Marks.empty cat z [(all_chars, [])] in
List.fold_right
(fun (s, y) rem ->
match
first (function E.TMatch marks -> Some marks | _ -> None)
y
with
None ->
Cset.prepend s (E.tseq kind y z []) rem
| Some marks ->
let z'' = prepend_marks marks z' in
match kind with
`Longest ->
Cset.prepend s (E.tseq kind (remove_matches y) z []) (
prepend_deriv (restrict s z'') rem)
| `Shortest ->
prepend_deriv (restrict s z'') (
Cset.prepend s (E.tseq kind (remove_matches y) z []) rem)
| `First ->
let (y', y'') = split_at_match y in
Cset.prepend s (E.tseq kind y' z []) (
prepend_deriv (restrict s z'') (
Cset.prepend s (E.tseq kind y'' z []) rem)))
y rem
else
List.fold_right
(fun (s, xl) rem -> Cset.prepend s (E.tseq kind xl z []) rem) y rem
let rec deriv_3 all_chars categories cat x rem =
match x with
E.TSeq (y, z, kind) ->
let y' = deriv_4 all_chars categories cat y [(all_chars, [])] in
deriv_seq all_chars categories cat kind y' z rem
| E.TExp (marks, e) ->
deriv_1 all_chars categories marks cat e rem
| E.TMatch _ ->
Cset.prepend all_chars [x] rem
and deriv_4 all_chars categories cat l rem =
match l with
[] -> rem
| y :: r -> deriv_3 all_chars categories cat y
(deriv_4 all_chars categories cat r rem)
let deriv tbl_ref all_chars categories st =
let der = deriv_4 all_chars categories st.State.category st.State.desc
[(all_chars, [])] in
simpl_tr (
List.fold_right (fun (s, expr) rem ->
let (expr', _) = remove_duplicates [] expr eps_expr in
(*
Format.eprintf "@[<3>@[%a@]: %a / %a@]@." Cset.print s print_state expr print_state expr';
*)
let idx = free_index tbl_ref expr' in
let expr'' = set_idx idx expr' in
List.fold_right (fun (cat', s') rem ->
let s'' = Cset.inter s s' in
if Cset.is_empty s''
then rem
else (s'', State.mk idx cat' expr'') :: rem)
categories rem) der [])
(****)
let flatten_match m =
let ma = List.fold_left (fun ma (i, _) -> max ma i) (-1) m in
let res = Array.make (ma + 1) (-1) in
List.iter (fun (i, v) -> res.(i) <- v) m;
res
let status s =
match s.State.status with
Some st ->
st
| None ->
let st =
match s.State.desc with
[] -> Failed
| E.TMatch m :: _ -> Match (flatten_match m.Marks.marks, m.Marks.pmarks)
| _ -> Running
in
s.State.status <- Some st;
st

View file

@ -0,0 +1,101 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(* Regular expressions *)
type mark = int
type sem = [ `Longest | `Shortest | `First ]
type rep_kind = [ `Greedy | `Non_greedy ]
val pp_sem : Format.formatter -> sem -> unit
val pp_rep_kind : Format.formatter -> rep_kind -> unit
type expr
val is_eps : expr -> bool
val pp : Format.formatter -> expr -> unit
type ids
val create_ids : unit -> ids
val cst : ids -> Cset.t -> expr
val empty : ids -> expr
val alt : ids -> expr list -> expr
val seq : ids -> sem -> expr -> expr -> expr
val eps : ids -> expr
val rep : ids -> rep_kind -> sem -> expr -> expr
val mark : ids -> mark -> expr
val pmark : ids -> Pmark.t -> expr
val erase : ids -> mark -> mark -> expr
val before : ids -> Category.t -> expr
val after : ids -> Category.t -> expr
val rename : ids -> expr -> expr
(****)
(* States of the automata *)
type idx = int
module Marks : sig
type t =
{ marks: (mark * idx) list
; pmarks: Pmark.Set.t }
end
module E : sig
type t
val pp : Format.formatter -> t -> unit
end
type hash
type mark_infos = int array
type status = Failed | Match of mark_infos * Pmark.Set.t | Running
module State : sig
type t =
{ idx: idx
; category: Category.t
; desc: E.t list
; mutable status: status option
; hash: hash }
val dummy : t
val create : Category.t -> expr -> t
module Table : Hashtbl.S with type key = t
end
(****)
(* Computation of the states following a given state *)
type working_area
val create_working_area : unit -> working_area
val index_count : working_area -> int
val delta : working_area -> Category.t -> Cset.c -> State.t -> State.t
val deriv :
working_area -> Cset.t -> (Category.t * Cset.t) list -> State.t ->
(Cset.t * State.t) list
(****)
val status : State.t -> status

View file

@ -0,0 +1,27 @@
type t = int
let equal (x : int) (y : int) = x = y
let compare (x : int) (y : int) = compare x y
let to_int x = x
let pp = Format.pp_print_int
let intersect x y = x land y <> 0
let (++) x y = x lor y
let dummy = -1
let inexistant = 1
let letter = 2
let not_letter = 4
let newline = 8
let lastnewline = 16
let search_boundary = 32
let from_char = function
(* Should match [cword] definition *)
| 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '\170' | '\181' | '\186'
| '\192'..'\214' | '\216'..'\246' | '\248'..'\255' ->
letter
| '\n' ->
not_letter ++ newline
| _ ->
not_letter

View file

@ -0,0 +1,25 @@
(** Categories represent the various kinds of characters that can be tested
by look-ahead and look-behind operations.
This is more restricted than Cset, but faster.
*)
type t
val (++) : t -> t -> t
val from_char : char -> t
val dummy : t
val inexistant : t
val letter : t
val not_letter : t
val newline : t
val lastnewline : t
val search_boundary : t
val to_int : t -> int
val equal : t -> t -> bool
val compare : t -> t -> int
val intersect : t -> t -> bool
val pp : Format.formatter -> t -> unit

View file

@ -0,0 +1,34 @@
(* In reality, this can really be represented as a bool array.
The representation is best thought of as a list of all chars along with a
flag:
(a, 0), (b, 1), (c, 0), (d, 0), ...
characters belonging to the same color are represented by sequnces of
characters with the flag set to 0.
*)
type t = Bytes.t
let make () = Bytes.make 257 '\000'
let flatten cm =
let c = Bytes.create 256 in
let color_repr = Bytes.create 256 in
let v = ref 0 in
Bytes.set c 0 '\000';
Bytes.set color_repr 0 '\000';
for i = 1 to 255 do
if Bytes.get cm i <> '\000' then incr v;
Bytes.set c i (Char.chr !v);
Bytes.set color_repr !v (Char.chr i)
done;
(Bytes.unsafe_to_string c, Bytes.sub_string color_repr 0 (!v + 1), !v + 1)
(* mark all the endpoints of the intervals of the char set with the 1 byte *)
let split s cm =
Cset.iter s ~f:(fun i j ->
Bytes.set cm i '\001';
Bytes.set cm (j + 1) '\001';
)

View file

@ -0,0 +1,14 @@
(* Color maps exists to provide an optimization for the regex engine. The fact
that some characters are entirely equivalent for some regexes means that we
can use them interchangeably.
A color map assigns a color to every character in our character set. Any two
characters with the same color will be treated equivalently by the automaton.
*)
type t
val make : unit -> t
val flatten : t -> string * string * int
val split : Cset.t -> t -> unit

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,749 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(** Module [Re]: code for creating and using regular expressions,
independently of regular expression syntax. *)
type t
(** Regular expression *)
type re
(** Compiled regular expression *)
(** Manipulate matching groups. *)
module Group : sig
type t
(** Information about groups in a match. As is conventional, every
match implicitly has a group 0 that covers the whole match, and
explicit groups are numbered from 1. *)
val get : t -> int -> string
(** Raise [Not_found] if the group did not match *)
val get_opt : t -> int -> string option
(** Similar to {!get}, but returns an option instead of using an exception. *)
val offset : t -> int -> int * int
(** Raise [Not_found] if the group did not match *)
val start : t -> int -> int
(** Return the start of the match. Raise [Not_found] if the group did not match. *)
val stop : t -> int -> int
(** Return the end of the match. Raise [Not_found] if the group did not match. *)
val all : t -> string array
(** Return the empty string for each group which did not match *)
val all_offset : t -> (int * int) array
(** Return [(-1,-1)] for each group which did not match *)
val test : t -> int -> bool
(** Test whether a group matched *)
val nb_groups : t -> int
(** Returns the total number of groups defined - matched or not.
This function is experimental. *)
val pp : Format.formatter -> t -> unit
end
type groups = Group.t [@@ocaml.deprecated "Use Group.t"]
(** {2 Compilation and execution of a regular expression} *)
val compile : t -> re
(** Compile a regular expression into an executable version that can be
used to match strings, e.g. with {!exec}. *)
val group_count : re -> int
(** Return the number of capture groups (including the one
corresponding to the entire regexp). *)
val group_names : re -> (string * int) list
(** Return named capture groups with their index. *)
val exec :
?pos:int -> (** Default: 0 *)
?len:int -> (** Default: -1 (until end of string) *)
re -> string -> Group.t
(** [exec re str] searches [str] for a match of the compiled expression [re],
and returns the matched groups if any.
More specifically, when a match exists, [exec] returns a match that
starts at the earliest position possible. If multiple such matches are
possible, the one specified by the match semantics described below is
returned.
{5 Examples:}
{[
# let regex = Re.compile Re.(seq [str "//"; rep print ]);;
val regex : re = <abstr>
# Re.exec regex "// a C comment";;
- : Re.substrings = <abstr>
# Re.exec regex "# a C comment?";;
Exception: Not_found
# Re.exec ~pos:1 regex "// a C comment";;
Exception: Not_found
]}
@param pos optional beginning of the string (default 0)
@param len length of the substring of [str] that can be matched (default [-1],
meaning to the end of the string)
@raise Not_found if the regular expression can't be found in [str]
*)
val exec_opt :
?pos:int -> (** Default: 0 *)
?len:int -> (** Default: -1 (until end of string) *)
re -> string -> Group.t option
(** Similar to {!exec}, but returns an option instead of using an exception.
{5 Examples:}
{[
# let regex = Re.compile Re.(seq [str "//"; rep print ]);;
val regex : re = <abstr>
# Re.exec_opt regex "// a C comment";;
- : Re.substrings option = Some <abstr>
# Re.exec_opt regex "# a C comment?";;
- : Re.substrings option = None
# Re.exec_opt ~pos:1 regex "// a C comment";;
- : Re.substrings option = None
]}
*)
val execp :
?pos:int -> (** Default: 0 *)
?len:int -> (** Default: -1 (until end of string) *)
re -> string -> bool
(** Similar to {!exec}, but returns [true] if the expression matches,
and [false] if it doesn't. This function is more efficient than
calling {!exec} or {!exec_opt} and ignoring the returned group.
{5 Examples:}
{[
# let regex = Re.compile Re.(seq [str "//"; rep print ]);;
val regex : re = <abstr>
# Re.execp regex "// a C comment";;
- : bool = true
# Re.execp ~pos:1 regex "// a C comment";;
- : bool = false
]}
*)
val exec_partial :
?pos:int -> (** Default: 0 *)
?len:int -> (** Default: -1 (until end of string) *)
re -> string -> [ `Full | `Partial | `Mismatch ]
(** More detailed version of {!execp}. [`Full] is equivalent to [true],
while [`Mismatch] and [`Partial] are equivalent to [false], but [`Partial]
indicates the input string could be extended to create a match.
{5 Examples:}
{[
# let regex = Re.compile Re.(seq [bos; str "// a C comment"]);;
val regex : re = <abstr>
# Re.exec_partial regex "// a C comment here.";;
- : [ `Full | `Mismatch | `Partial ] = `Full
# Re.exec_partial regex "// a C comment";;
- : [ `Full | `Mismatch | `Partial ] = `Partial
# Re.exec_partial regex "//";;
- : [ `Full | `Mismatch | `Partial ] = `Partial
# Re.exec_partial regex "# a C comment?";;
- : [ `Full | `Mismatch | `Partial ] = `Mismatch
]}
*)
val exec_partial_detailed :
?pos:int -> (** Default: 0 *)
?len:int -> (** Default: -1 (until end of string) *)
re -> string -> [ `Full of Group.t | `Partial of int | `Mismatch ]
(** More detailed version of {!exec_opt}. [`Full group] is equivalent to [Some group],
while [`Mismatch] and [`Partial _] are equivalent to [None], but [`Partial position]
indicates that the input string could be extended to create a match, and no match could
start in the input string before the given position.
This could be used to not have to search the entirety of the input if more
becomes available, and use the given position as the [?pos] argument.
*)
(** Marks *)
module Mark : sig
type t
(** Mark id *)
val test : Group.t -> t -> bool
(** Tell if a mark was matched. *)
module Set : Set.S with type elt = t
val all : Group.t -> Set.t
(** Return all the mark matched. *)
val equal : t -> t -> bool
val compare : t -> t -> int
end
(** {2 High Level Operations} *)
type split_token =
[ `Text of string (** Text between delimiters *)
| `Delim of Group.t (** Delimiter *)
]
val all : ?pos:int -> ?len:int -> re -> string -> Group.t list
(** Repeatedly calls {!exec} on the given string, starting at given position and
length.
{5 Examples:}
{[
# let regex = Re.compile Re.(seq [str "my"; blank; word(rep alpha)]);;
val regex : re = <abstr>
# Re.all regex "my head, my shoulders, my knees, my toes ...";;
- : Re.substrings list = [<abstr>; <abstr>; <abstr>; <abstr>]
# Re.all regex "My head, My shoulders, My knees, My toes ...";;
- : Re.substrings list = []
]}
*)
type 'a gen = unit -> 'a option
val all_gen : ?pos:int -> ?len:int -> re -> string -> Group.t gen
[@@ocaml.deprecated "Use Seq.all"]
(** @deprecated Use {!module-Seq.all} instead. *)
val all_seq : ?pos:int -> ?len:int -> re -> string -> Group.t Seq.t
[@@ocaml.deprecated "Use Seq.all"]
(** @deprecated Use {!module-Seq.all} instead. *)
val matches : ?pos:int -> ?len:int -> re -> string -> string list
(** Same as {!all}, but extracts the matched substring rather than returning
the whole group. This basically iterates over matched strings.
{5 Examples:}
{[
# let regex = Re.compile Re.(seq [str "my"; blank; word(rep alpha)]);;
val regex : re = <abstr>
# Re.matches regex "my head, my shoulders, my knees, my toes ...";;
- : string list = ["my head"; "my shoulders"; "my knees"; "my toes"]
# Re.matches regex "My head, My shoulders, My knees, My toes ...";;
- : string list = []
# Re.matches regex "my my my my head my 1 toe my ...";;
- : string list = ["my my"; "my my"]
# Re.matches ~pos:2 regex "my my my my head my +1 toe my ...";;
- : string list = ["my my"; "my head"]
]}
*)
val matches_gen : ?pos:int -> ?len:int -> re -> string -> string gen
[@@ocaml.deprecated "Use Seq.matches"]
(** @deprecated Use {!module-Seq.matches} instead. *)
val matches_seq : ?pos:int -> ?len:int -> re -> string -> string Seq.t
[@@ocaml.deprecated "Use Seq.matches"]
(** @deprecated Use {!module-Seq.matches} instead. *)
val split : ?pos:int -> ?len:int -> re -> string -> string list
(** [split re s] splits [s] into chunks separated by [re]. It yields the chunks
themselves, not the separator.
{5 Examples:}
{[
# let regex = Re.compile (Re.char ',');;
val regex : re = <abstr>
# Re.split regex "Re,Ocaml,Jerome Vouillon";;
- : string list = ["Re"; "Ocaml"; "Jerome Vouillon"]
# Re.split regex "No commas in this sentence.";;
- : string list = ["No commas in this sentence."]
# Re.split ~pos:3 regex "1,2,3,4. Commas go brrr.";;
- : string list = ["3"; "4. Commas go brrr."]
]}
*)
val split_gen : ?pos:int -> ?len:int -> re -> string -> string gen
[@@ocaml.deprecated "Use Seq.split"]
(** @deprecated Use {!module-Seq.split} instead. *)
val split_seq : ?pos:int -> ?len:int -> re -> string -> string Seq.t
[@@ocaml.deprecated "Use Seq.split"]
(** @deprecated Use {!module-Seq.split} instead. *)
val split_full : ?pos:int -> ?len:int -> re -> string -> split_token list
(** [split re s] splits [s] into chunks separated by [re]. It yields the chunks
along with the separators. For instance this can be used with a
whitespace-matching re such as ["[\t ]+"].
{5 Examples:}
{[
# let regex = Re.compile (Re.char ',');;
val regex : re = <abstr>
# Re.split_full regex "Re,Ocaml,Jerome Vouillon";;
- : Re.split_token list =
[`Text "Re"; `Delim <abstr>; `Text "Ocaml"; `Delim <abstr>;
`Text "Jerome Vouillon"]
# Re.split_full regex "No commas in this sentence.";;
- : Re.split_token list = [`Text "No commas in this sentence."]
# Re.split_full ~pos:3 regex "1,2,3,4. Commas go brrr.";;
- : Re.split_token list =
[`Delim <abstr>; `Text "3"; `Delim <abstr>; `Text "4. Commas go brrr."]
]}
*)
val split_full_gen : ?pos:int -> ?len:int -> re -> string -> split_token gen
[@@ocaml.deprecated "Use Seq.split_full"]
(** @deprecated Use {!module-Seq.split_full} instead. *)
val split_full_seq : ?pos:int -> ?len:int -> re -> string -> split_token Seq.t
[@@ocaml.deprecated "Use Seq.split_full"]
(** @deprecated Use {!module-Seq.split_full} instead. *)
module Seq : sig
val all :
?pos:int -> (** Default: 0 *)
?len:int ->
re -> string -> Group.t Seq.t
(** Same as {!module-Re.val-all} but returns an iterator.
{5 Examples:}
{[
# let regex = Re.compile Re.(seq [str "my"; blank; word(rep alpha)]);;
val regex : re = <abstr>
# Re.Seq.all regex "my head, my shoulders, my knees, my toes ...";;
- : Re.substrings Seq.t = <fun>
]}
@since 1.10.0 *)
val matches :
?pos:int -> (** Default: 0 *)
?len:int ->
re -> string -> string Seq.t
(** Same as {!module-Re.val-matches}, but returns an iterator.
{5 Example:}
{[
# let regex = Re.compile Re.(seq [str "my"; blank; word(rep alpha)]);;
val regex : re = <abstr>
# Re.Seq.matches regex "my head, my shoulders, my knees, my toes ...";;
- : string Seq.t = <fun>
]}
@since 1.10.0 *)
val split :
?pos:int -> (** Default: 0 *)
?len:int ->
re -> string -> string Seq.t
(** Same as {!module-Re.val-split} but returns an iterator.
{5 Example:}
{[
# let regex = Re.compile (Re.char ',');;
val regex : re = <abstr>
# Re.Seq.split regex "Re,Ocaml,Jerome Vouillon";;
- : string Seq.t = <fun>
]}
@since 1.10.0 *)
val split_full :
?pos:int -> (** Default: 0 *)
?len:int ->
re -> string -> split_token Seq.t
(** Same as {!module-Re.val-split_full} but returns an iterator.
{5 Example:}
{[
# let regex = Re.compile (Re.char ',');;
val regex : re = <abstr>
# Re.Seq.split_full regex "Re,Ocaml,Jerome Vouillon";;
- : Re__Core.split_token Seq.t = <fun>
]}
@since 1.10.0 *)
end
val replace :
?pos:int -> (** Default: 0 *)
?len:int ->
?all:bool -> (** Default: true. Otherwise only replace first occurrence *)
re -> (** matched groups *)
f:(Group.t -> string) -> (** how to replace *)
string -> (** string to replace in *)
string
(** [replace ~all re ~f s] iterates on [s], and replaces every occurrence
of [re] with [f substring] where [substring] is the current match.
If [all = false], then only the first occurrence of [re] is replaced. *)
val replace_string :
?pos:int -> (** Default: 0 *)
?len:int ->
?all:bool -> (** Default: true. Otherwise only replace first occurrence *)
re -> (** matched groups *)
by:string -> (** replacement string *)
string -> (** string to replace in *)
string
(** [replace_string ~all re ~by s] iterates on [s], and replaces every
occurrence of [re] with [by]. If [all = false], then only the first
occurrence of [re] is replaced.
{5 Examples:}
{[
# let regex = Re.compile (Re.char ',');;
val regex : re = <abstr>
# Re.replace_string regex ~by:";" "[1,2,3,4,5,6,7]";;
- : string = "[1;2;3;4;5;6;7]"
# Re.replace_string regex ~all:false ~by:";" "[1,2,3,4,5,6,7]";;
- : string = "[1;2,3,4,5,6,7]"
]}
*)
(** {2 String expressions (literal match)} *)
val str : string -> t
val char : char -> t
(** {2 Basic operations on regular expressions} *)
val alt : t list -> t
(** Alternative.
[alt []] is equivalent to {!empty}.
By default, the leftmost match is preferred (see match semantics below).
*)
val seq : t list -> t
(** Sequence *)
val empty : t
(** Match nothing *)
val epsilon : t
(** Empty word *)
val rep : t -> t
(** 0 or more matches *)
val rep1 : t -> t
(** 1 or more matches *)
val repn : t -> int -> int option -> t
(** [repn re i j] matches [re] at least [i] times
and at most [j] times, bounds included.
[j = None] means no upper bound.
*)
val opt : t -> t
(** 0 or 1 matches *)
(** {2 String, line, word}
We define a word as a sequence of latin1 letters, digits and underscore.
*)
val bol : t
(** Beginning of line *)
val eol : t
(** End of line *)
val bow : t
(** Beginning of word *)
val eow : t
(** End of word *)
val bos : t
(** Beginning of string. This differs from {!start} because it matches
the beginning of the input string even when using [~pos] arguments:
{[
let b = execp (compile (seq [ bos; str "a" ])) "aa" ~pos:1 in
assert (not b)
]}
*)
val eos : t
(** End of string. This is different from {!stop} in the way described
in {!bos}. *)
val leol : t
(** Last end of line or end of string *)
val start : t
(** Initial position. This differs from {!bos} because it takes into
account the [~pos] arguments:
{[
let b = execp (compile (seq [ start; str "a" ])) "aa" ~pos:1 in
assert b
]}
*)
val stop : t
(** Final position. This is different from {!eos} in the way described
in {!start}. *)
val word : t -> t
(** Word *)
val not_boundary : t
(** Not at a word boundary *)
val whole_string : t -> t
(** Only matches the whole string, i.e. [fun t -> seq [ eos; t; bos ]]. *)
(** {2 Match semantics}
A regular expression frequently matches a string in multiple ways. For
instance [exec (compile (opt (str "a"))) "ab"] can match "" or "a". Match
semantic can be modified with the functions below, allowing one to choose
which of these is preferable.
By default, the leftmost branch of alternations is preferred, and repetitions
are greedy.
Note that the existence of matches cannot be changed by specifying match
semantics. [seq [ bos; str "a"; non_greedy (opt (str "b")); eos ]] will
match when applied to "ab". However if [seq [ bos; str "a"; non_greedy (opt
(str "b")) ]] is applied to "ab", it will match "a" rather than "ab".
Also note that multiple match semantics can conflict. In this case, the one
executed earlier takes precedence. For instance, any match of [shortest (seq
[ bos; group (rep (str "a")); group (rep (str "a")); eos ])] will always have
an empty first group. Conversely, if we use [longest] instead of [shortest],
the second group will always be empty.
*)
val longest : t -> t
(** Longest match semantics. That is, matches will match as many bytes as
possible. If multiple choices match the maximum amount of bytes, the one
respecting the inner match semantics is preferred. *)
val shortest : t -> t
(** Same as {!longest}, but matching the least number of bytes. *)
val first : t -> t
(** First match semantics for alternations (not repetitions). That is, matches
will prefer the leftmost branch of the alternation that matches the text. *)
val greedy : t -> t
(** Greedy matches for repetitions ({!opt}, {!rep}, {!rep1}, {!repn}): they will
match as many times as possible. *)
val non_greedy : t -> t
(** Non-greedy matches for repetitions ({!opt}, {!rep}, {!rep1}, {!repn}): they
will match as few times as possible. *)
(** {2 Groups (or submatches)} *)
val group : ?name:string -> t -> t
(** Delimit a group. The group is considered as matching if it is used at least
once (it may be used multiple times if is nested inside {!rep} for
instance). If it is used multiple times, the last match is what gets
captured. *)
val no_group : t -> t
(** Remove all groups *)
val nest : t -> t
(** When matching against [nest e], only the group matching in the
last match of e will be considered as matching.
For instance:
{[
let re = compile (rep1 (nest (alt [ group (str "a"); str "b" ]))) in
let group = Re.exec re "ab" in
assert (Group.get_opt group 1 = None);
(* same thing but without [nest] *)
let re = compile (rep1 (alt [ group (str "a"); str "b" ])) in
let group = Re.exec re "ab" in
assert (Group.get_opt group 1 = Some "a");
]}
*)
val mark : t -> Mark.t * t
(** Mark a regexp. the markid can then be used to know if this regexp was used. *)
(** {2 Character sets} *)
val set : string -> t
(** Any character of the string *)
val rg : char -> char -> t
(** Character ranges *)
val inter : t list -> t
(** Intersection of character sets *)
val diff : t -> t -> t
(** Difference of character sets *)
val compl : t list -> t
(** Complement of union *)
(** {2 Predefined character sets} *)
val any : t
(** Any character *)
val notnl : t
(** Any character but a newline *)
val alnum : t
val wordc : t
val alpha : t
val ascii : t
val blank : t
val cntrl : t
val digit : t
val graph : t
val lower : t
val print : t
val punct : t
val space : t
val upper : t
val xdigit : t
(** {2 Case modifiers} *)
val case : t -> t
(** Case sensitive matching. Note that this works on latin1, not ascii and not
utf8. *)
val no_case : t -> t
(** Case insensitive matching. Note that this works on latin1, not ascii and not
utf8. *)
(****)
(** {2 Internal debugging} *)
val pp : Format.formatter -> t -> unit
val pp_re : Format.formatter -> re -> unit
(** Alias for {!pp_re}. Deprecated *)
val print_re : Format.formatter -> re -> unit
module View : sig
type outer
(** A view of the top-level of a regex. This type is unstable and may change *)
type t =
Set of Cset.t
| Sequence of outer list
| Alternative of outer list
| Repeat of outer * int * int option
| Beg_of_line | End_of_line
| Beg_of_word | End_of_word | Not_bound
| Beg_of_str | End_of_str
| Last_end_of_line | Start | Stop
| Sem of Automata.sem * outer
| Sem_greedy of Automata.rep_kind * outer
| Group of string option * outer | No_group of outer | Nest of outer
| Case of outer | No_case of outer
| Intersection of outer list
| Complement of outer list
| Difference of outer * outer
| Pmark of Pmark.t * outer
val view : outer -> t
end with type outer := t
(** {2 Experimental functions} *)
val witness : t -> string
(** [witness r] generates a string [s] such that [execp (compile r) s] is true.
Be warned that this function is buggy because it ignores zero-width
assertions like beginning of words. As a result it can generate incorrect
results. *)
(** {2 Deprecated functions} *)
type substrings = Group.t
[@@ocaml.deprecated "Use Group.t"]
(** Alias for {!Group.t}. Deprecated *)
val get : Group.t -> int -> string
[@@ocaml.deprecated "Use Group.get"]
(** Same as {!Group.get}. Deprecated *)
val get_ofs : Group.t -> int -> int * int
[@@ocaml.deprecated "Use Group.offset"]
(** Same as {!Group.offset}. Deprecated *)
val get_all : Group.t -> string array
[@@ocaml.deprecated "Use Group.all"]
(** Same as {!Group.all}. Deprecated *)
val get_all_ofs : Group.t -> (int * int) array
[@@ocaml.deprecated "Use Group.all_offset"]
(** Same as {!Group.all_offset}. Deprecated *)
val test : Group.t -> int -> bool
[@@ocaml.deprecated "Use Group.test"]
(** Same as {!Group.test}. Deprecated *)
type markid = Mark.t
[@@ocaml.deprecated "Use Mark."]
(** Alias for {!Mark.t}. Deprecated *)
val marked : Group.t -> Mark.t -> bool
[@@ocaml.deprecated "Use Mark.test"]
(** Same as {!Mark.test}. Deprecated *)
val mark_set : Group.t -> Mark.Set.t
[@@ocaml.deprecated "Use Mark.all"]
(** Same as {!Mark.all}. Deprecated *)

View file

@ -0,0 +1,157 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
type c = int
type t = (c * c) list
let rec union l l' =
match l, l' with
_, [] -> l
| [], _ -> l'
| (c1, c2)::r, (c1', c2')::r' ->
if c2 + 1 < c1' then
(c1, c2)::union r l'
else if c2' + 1 < c1 then
(c1', c2')::union l r'
else if c2 < c2' then
union r ((min c1 c1', c2')::r')
else
union ((min c1 c1', c2)::r) r'
let rec inter l l' =
match l, l' with
_, [] -> []
| [], _ -> []
| (c1, c2)::r, (c1', c2')::r' ->
if c2 < c1' then
inter r l'
else if c2' < c1 then
inter l r'
else if c2 < c2' then
(max c1 c1', c2)::inter r l'
else
(max c1 c1', c2')::inter l r'
let rec diff l l' =
match l, l' with
_, [] -> l
| [], _ -> []
| (c1, c2)::r, (c1', c2')::r' ->
if c2 < c1' then
(c1, c2)::diff r l'
else if c2' < c1 then
diff l r'
else
let r'' = if c2' < c2 then (c2' + 1, c2) :: r else r in
if c1 < c1' then
(c1, c1' - 1)::diff r'' r'
else
diff r'' r'
let single c = [c, c]
let add c l = union (single c) l
let seq c c' = if c <= c' then [c, c'] else [c', c]
let rec offset o l =
match l with
[] -> []
| (c1, c2) :: r -> (c1 + o, c2 + o) :: offset o r
let empty = []
let rec mem (c : int) s =
match s with
[] -> false
| (c1, c2) :: rem -> if c <= c2 then c >= c1 else mem c rem
(****)
type hash = int
let rec hash_rec = function
| [] -> 0
| (i, j)::r -> i + 13 * j + 257 * hash_rec r
let hash l = (hash_rec l) land 0x3FFFFFFF
(****)
let print_one ch (c1, c2) =
if c1 = c2 then
Format.fprintf ch "%d" c1
else
Format.fprintf ch "%d-%d" c1 c2
let pp = Fmt.list print_one
let rec iter t ~f =
match t with
| [] -> ()
| (x, y)::xs ->
f x y;
iter xs ~f
let one_char = function
| [i, j] when i = j -> Some i
| _ -> None
module CSetMap = Map.Make (struct
type t = int * (int * int) list
let compare (i, u) (j, v) =
let c = compare i j in
if c <> 0
then c
else compare u v
end)
let fold_right t ~init ~f = List.fold_right f t init
let csingle c = single (Char.code c)
let cany = [0, 255]
let is_empty = function
| [] -> true
| _ -> false
let rec prepend s x l =
match s, l with
| [], _ -> l
| _r, [] -> []
| (_c, c') :: r, ([d, _d'], _x') :: _r' when c' < d -> prepend r x l
| (c, c') :: r, ([d, d'], x') :: r' ->
if c <= d then begin
if c' < d'
then ([d, c'], x @ x') :: prepend r x (([c' + 1, d'], x') :: r')
else ([d, d'], x @ x') :: prepend s x r'
end else begin
if c > d'
then ([d, d'], x') :: prepend s x r'
else ([d, c - 1], x') :: prepend s x (([c, d'], x') :: r')
end
| _ -> assert false
let pick = function
| [] -> invalid_arg "Re_cset.pick"
| (x, _)::_ -> x

View file

@ -0,0 +1,63 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(* Character sets, represented as sorted list of intervals *)
type c = int
type t
val iter : t -> f:(c -> c -> unit) -> unit
val union : t -> t -> t
val inter : t -> t -> t
val diff : t -> t -> t
val offset : int -> t -> t
val empty : t
val single : c -> t
val seq : c -> c -> t
val add : c -> t -> t
val mem : c -> t -> bool
type hash
val hash : t -> hash
val pp : Format.formatter -> t -> unit
val one_char : t -> c option
val fold_right : t -> init:'acc -> f:(c * c -> 'acc -> 'acc) -> 'acc
val hash_rec : t -> int
module CSetMap : Map.S with type key = int * t
val cany : t
val csingle : char -> t
val is_empty : t -> bool
val prepend : t -> 'a list -> (t * 'a list) list -> (t * 'a list) list
val pick : t -> c

View file

@ -0,0 +1,4 @@
(library
(name dune_re)
(public_name dune-private-libs.dune_re)
(synopsis "Internal Dune library, do not use!"))

View file

@ -0,0 +1 @@
include Re

View file

@ -0,0 +1,124 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
module Re = Core
exception Parse_error
exception Not_supported
let parse s =
let i = ref 0 in
let l = String.length s in
let eos () = !i = l in
let test c = not (eos ()) && s.[!i] = c in
let test2 c c' = !i + 1 < l && s.[!i] = c && s.[!i + 1] = c' in
let accept c = let r = test c in if r then incr i; r in
let accept2 c c' = let r = test2 c c' in if r then i := !i + 2; r in
let get () = let r = s.[!i] in incr i; r in
let rec regexp () = regexp' (branch ())
and regexp' left =
if accept2 '\\' '|' then regexp' (Re.alt [left; branch ()]) else left
and branch () = branch' []
and branch' left =
if eos () || test2 '\\' '|' || test2 '\\' ')' then Re.seq (List.rev left)
else branch' (piece () :: left)
and piece () =
let r = atom () in
if accept '*' then Re.rep r else
if accept '+' then Re.rep1 r else
if accept '?' then Re.opt r else
r
and atom () =
if accept '.' then begin
Re.notnl
end else if accept '^' then begin
Re.bol
end else if accept '$' then begin
Re.eol
end else if accept '[' then begin
if accept '^' then
Re.compl (bracket [])
else
Re.alt (bracket [])
end else if accept '\\' then begin
if accept '(' then begin
let r = regexp () in
if not (accept2 '\\' ')') then raise Parse_error;
Re.group r
end else if accept '`' then
Re.bos
else if accept '\'' then
Re.eos
else if accept '=' then
Re.start
else if accept 'b' then
Re.alt [Re.bow; Re.eow]
else if accept 'B' then
Re.not_boundary
else if accept '<' then
Re.bow
else if accept '>' then
Re.eow
else if accept 'w' then
Re.alt [Re.alnum; Re.char '_']
else if accept 'W' then
Re.compl [Re.alnum; Re.char '_']
else begin
if eos () then raise Parse_error;
match get () with
'*' | '+' | '?' | '[' | ']' | '.' | '^' | '$' | '\\' as c ->
Re.char c
| '0' .. '9' ->
raise Not_supported
| _ ->
raise Parse_error
end
end else begin
if eos () then raise Parse_error;
match get () with
'*' | '+' | '?' -> raise Parse_error
| c -> Re.char c
end
and bracket s =
if s <> [] && accept ']' then s else begin
let c = char () in
if accept '-' then begin
if accept ']' then Re.char c :: Re.char '-' :: s else begin
let c' = char () in
bracket (Re.rg c c' :: s)
end
end else
bracket (Re.char c :: s)
end
and char () =
if eos () then raise Parse_error;
get ()
in
let res = regexp () in
if not (eos ()) then raise Parse_error;
res
let re ?(case = true) s = let r = parse s in if case then r else Re.no_case r
let compile = Re.compile
let compile_pat ?(case = true) s = compile (re ~case s)

View file

@ -0,0 +1,37 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(** Emacs-style regular expressions *)
exception Parse_error
exception Not_supported
(** Errors that can be raised during the parsing of the regular expression *)
val re : ?case:bool -> string -> Core.t
(** Parsing of an Emacs-style regular expression *)
val compile : Core.t -> Core.re
(** Regular expression compilation *)
val compile_pat : ?case:bool -> string -> Core.re
(** Same as [Core.compile] *)

View file

@ -0,0 +1,35 @@
(** Very small tooling for format printers. *)
include Format
type 'a t = Format.formatter -> 'a -> unit
let list = pp_print_list
let str = pp_print_string
let sexp fmt s pp x = fprintf fmt "@[<3>(%s@ %a)@]" s pp x
let pair pp1 pp2 fmt (v1,v2) =
pp1 fmt v1; pp_print_space fmt () ; pp2 fmt v2
let triple pp1 pp2 pp3 fmt (v1, v2, v3) =
pp1 fmt v1; pp_print_space fmt () ;
pp2 fmt v2; pp_print_space fmt () ;
pp3 fmt v3
let int = pp_print_int
let optint fmt = function
| None -> ()
| Some i -> fprintf fmt "@ %d" i
let quote fmt s = Format.fprintf fmt "\"%s\"" s
let pp_olist pp_elem fmt =
Format.fprintf fmt "@[<3>[@ %a@ ]@]"
(pp_print_list
~pp_sep:(fun fmt () -> fprintf fmt ";@ ")
pp_elem)
let pp_str_list = pp_olist quote
let to_to_string pp x =
let b = Buffer.create 16 in
let fmt = Format.formatter_of_buffer b in
pp fmt x;
Buffer.contents b

View file

@ -0,0 +1,352 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
module Re = Core
exception Parse_error
type enclosed =
| Char of char
| Range of char * char
type piece =
| Exactly of char
| Any_of of enclosed list
| Any_but of enclosed list
| One
| Many
| ManyMany
type t = piece list
let of_string ~double_asterisk s : t =
let i = ref 0 in
let l = String.length s in
let eos () = !i = l in
let read c =
let r = not (eos ()) && s.[!i] = c in
if r then incr i;
r
in
(**
[read_ahead pattern] will attempt to read [pattern] and will return [true] if it was successful.
If it fails, it will return [false] and not increment the read index.
*)
let read_ahead pattern =
let pattern_len = String.length pattern in
(* if the pattern we are looking for exeeds the remaining length of s, return false immediately *)
if !i + pattern_len >= l then
false
else
try
for j = 0 to pattern_len - 1 do
let found = not (eos ()) && s.[!i + j] = pattern.[j] in
if not found then raise_notrace Exit;
done;
i := !i + pattern_len;
true
with | Exit -> false
in
let char () =
ignore (read '\\' : bool);
if eos () then raise Parse_error;
let r = s.[!i] in
incr i;
r
in
let enclosed () : enclosed list =
let rec loop s =
(* This returns the list in reverse order, but order isn't important anyway *)
if s <> [] && read ']'
then s
else
let c = char () in
if not (read '-')
then loop (Char c :: s)
else if read ']'
then Char c :: Char '-' :: s
else
let c' = char () in
loop (Range (c, c') :: s)
in
loop []
in
let piece () =
if double_asterisk && read_ahead "/**" && not (eos ())
then ManyMany
else if read '*'
then if double_asterisk && read '*'
then ManyMany
else Many
else if read '?'
then One
else if not (read '[')
then Exactly (char ())
else if read '^' || read '!'
then Any_but (enclosed ())
else Any_of (enclosed ())
in
let rec loop pieces =
if eos ()
then List.rev pieces
else loop (piece () :: pieces)
in
loop []
let mul l l' =
List.flatten (List.map (fun s -> List.map (fun s' -> s ^ s') l') l)
let explode str =
let l = String.length str in
let rec expl inner s i acc beg =
if i >= l then begin
if inner then raise Parse_error;
(mul beg [String.sub str s (i - s)], i)
end else
match str.[i] with
| '\\' -> expl inner s (i + 2) acc beg
| '{' ->
let (t, i') = expl true (i + 1) (i + 1) [] [""] in
expl inner i' i' acc
(mul beg (mul [String.sub str s (i - s)] t))
| ',' when inner ->
expl inner (i + 1) (i + 1)
(mul beg [String.sub str s (i - s)] @ acc) [""]
| '}' when inner ->
(mul beg [String.sub str s (i - s)] @ acc, i + 1)
| _ ->
expl inner s (i + 1) acc beg
in
List.rev (fst (expl false 0 0 [] [""]))
module State = struct
type t = {
re_pieces : Re.t list; (* last piece at head of list. *)
remaining : piece list; (* last piece at tail of list. *)
am_at_start_of_pattern : bool; (* true at start of pattern *)
am_at_start_of_component : bool; (* true at start of pattern or immediately
after '/' *)
pathname : bool;
match_backslashes : bool;
period : bool;
}
let create ~period ~pathname ~match_backslashes remaining =
{
re_pieces = [];
am_at_start_of_pattern = true;
am_at_start_of_component = true;
pathname;
match_backslashes;
period;
remaining;
}
let explicit_period t =
t.period && (
t.am_at_start_of_pattern ||
(t.am_at_start_of_component && t.pathname)
)
let explicit_slash t = t.pathname
let slashes t =
if t.match_backslashes then ['/'; '\\'] else ['/']
let append ?(am_at_start_of_component=false) t piece =
{ t with
re_pieces = piece :: t.re_pieces;
am_at_start_of_pattern = false;
am_at_start_of_component;
}
let to_re t = Re.seq (List.rev t.re_pieces)
let next t =
match t.remaining with
| [] -> None
| piece :: remaining -> Some (piece, { t with remaining })
end
let one ~explicit_slash ~slashes ~explicit_period =
Re.compl (
List.concat [
if explicit_slash then List.map Re.char slashes else [];
if explicit_period then [Re.char '.'] else [];
]
)
let enclosed enclosed =
match enclosed with
| Char c -> Re.char c
| Range (low, high) -> Re.rg low high
let enclosed_set ~explicit_slash ~slashes ~explicit_period kind set =
let set = List.map enclosed set in
let enclosure =
match kind with
| `Any_of -> Re.alt set
| `Any_but -> Re.compl set
in
Re.inter [enclosure; one ~explicit_slash ~slashes ~explicit_period]
let exactly state c =
let slashes = State.slashes state in
let am_at_start_of_component = List.mem c slashes in
let chars = if am_at_start_of_component then slashes else [c] in
State.append state (Re.alt (List.map Re.char chars)) ~am_at_start_of_component
let many_many state =
let explicit_period = state.State.period && state.State.pathname in
let first_explicit_period = State.explicit_period state in
let slashes = State.slashes state in
let match_component ~explicit_period =
Re.seq [
one ~explicit_slash:true ~slashes ~explicit_period;
Re.rep (one ~explicit_slash:true ~slashes ~explicit_period:false);
]
in
(* We must match components individually when [period] flag is set,
making sure to not match ["foo/.bar"]. *)
State.append state (
Re.seq [
Re.opt (match_component ~explicit_period:first_explicit_period);
Re.rep (
Re.seq [
Re.alt (List.map Re.char slashes);
Re.opt (match_component ~explicit_period);
]
);
])
let many (state : State.t) =
let explicit_slash = State.explicit_slash state in
let explicit_period = State.explicit_period state in
let slashes = State.slashes state in
(* Whether we must explicitly match period depends on the surrounding characters, but
slashes are easy to explicit match. This conditional splits out some simple cases.
*)
if not explicit_period then begin
State.append state (Re.rep (one ~explicit_slash ~slashes ~explicit_period))
end else if not explicit_slash then begin
(* In this state, we explicitly match periods only at the very beginning *)
State.append state (Re.opt (
Re.seq [
one ~explicit_slash:false ~slashes ~explicit_period;
Re.rep (one ~explicit_slash:false ~slashes ~explicit_period:false);
]
))
end else begin
let not_empty =
Re.seq [
one ~explicit_slash:true ~slashes ~explicit_period:true;
Re.rep (one ~explicit_slash:true ~slashes ~explicit_period:false);
]
in
(* [maybe_empty] is the default translation of Many, except in some special cases.
*)
let maybe_empty = Re.opt not_empty in
let enclosed_set state kind set =
State.append state (Re.alt [
enclosed_set kind set ~explicit_slash:true ~slashes ~explicit_period:true;
Re.seq [
not_empty;
(* Since [not_empty] matched, subsequent dots are not leading. *)
enclosed_set kind set ~explicit_slash:true ~slashes ~explicit_period:false;
];
])
in
let rec lookahead state =
match State.next state with
| None -> State.append state maybe_empty
(* glob ** === glob * . *)
| Some (Many, state) -> lookahead state
| Some (Exactly c, state) ->
let state =
State.append state
(if c = '.'
then not_empty
else maybe_empty)
in
exactly state c
(* glob *? === glob ?* *)
| Some (One, state) -> State.append state not_empty
| Some (Any_of enclosed, state) -> enclosed_set state `Any_of enclosed
| Some (Any_but enclosed, state) -> enclosed_set state `Any_but enclosed
(* * then ** === ** *)
| Some (ManyMany, state) -> many_many state
in
lookahead state
end
let piece state piece =
let explicit_slash = State.explicit_slash state in
let explicit_period = State.explicit_period state in
let slashes = State.slashes state in
match piece with
| One -> State.append state (one ~explicit_slash ~slashes ~explicit_period)
| Many -> many state
| Any_of enclosed ->
State.append state (enclosed_set `Any_of ~explicit_slash ~slashes ~explicit_period enclosed)
| Any_but enclosed ->
State.append state (enclosed_set `Any_but ~explicit_slash ~slashes ~explicit_period enclosed)
| Exactly c -> exactly state c
| ManyMany -> many_many state
let glob ~pathname ~match_backslashes ~period glob =
let rec loop state =
match State.next state with
| None -> State.to_re state
| Some (p, state) -> loop (piece state p)
in
loop (State.create ~pathname ~match_backslashes ~period glob)
let glob
?(anchored = false)
?(pathname = true)
?(match_backslashes = false)
?(period = true)
?(expand_braces = false)
?(double_asterisk = true)
s
=
let to_re s =
let re = glob ~pathname ~match_backslashes ~period (of_string ~double_asterisk s) in
if anchored
then Re.whole_string re
else re
in
if expand_braces
then Re.alt (List.map to_re (explode s))
else to_re s
let glob' ?anchored period s = glob ?anchored ~period s
let globx ?anchored s = glob ?anchored ~expand_braces:true s
let globx' ?anchored period s = glob ?anchored ~expand_braces:true ~period s

View file

@ -0,0 +1,88 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(** Shell-style regular expressions *)
exception Parse_error
val glob :
?anchored:bool ->
?pathname:bool ->
?match_backslashes:bool ->
?period:bool ->
?expand_braces:bool ->
?double_asterisk:bool ->
string ->
Core.t
(** Implements the semantics of shells patterns. The returned regular
expression is unanchored by default.
Character '*' matches any sequence of characters and character
'?' matches a single character.
A sequence '[...]' matches any one of the enclosed characters.
A sequence '[^...]' or '[!...]' matches any character *but* the enclosed characters.
A backslash escapes the following character. The last character of the string cannot
be a backslash.
[anchored] controls whether the regular expression will only match entire
strings. Defaults to false.
[pathname]: If this flag is set, match a slash in string only with a slash in pattern
and not by an asterisk ('*') or a question mark ('?') metacharacter, nor by a bracket
expression ('[]') containing a slash. Defaults to true.
[match_backslashes]: If this flag is set, a forward slash will also match a
backslash (useful when globbing Windows paths). Note that a backslash in the
pattern will continue to escape the following character. Defaults to
[false].
[period]: If this flag is set, a leading period in string has to be matched exactly by
a period in pattern. A period is considered to be leading if it is the first
character in string, or if both [pathname] is set and the period immediately follows a
slash. Defaults to true.
If [expand_braces] is true, braced sets will expand into multiple globs,
e.g. a\{x,y\}b\{1,2\} matches axb1, axb2, ayb1, ayb2. As specified for bash, brace
expansion is purely textual and can be nested. Defaults to false.
[double_asterisk]: If this flag is set, double asterisks ('**') will match slash
characters, even if [pathname] is set. The [period] flag still applies. Default to
true. *)
val glob' : ?anchored:bool -> bool -> string -> Core.t
(** Same, but allows to choose whether dots at the beginning of a
file name need to be explicitly matched (true) or not (false)
@deprecated Use [glob ~period].
*)
val globx : ?anchored:bool -> string -> Core.t
(** This version of [glob] also recognizes the pattern \{..,..\}
@deprecated Prefer [glob ~expand_braces:true].
*)
val globx' : ?anchored:bool -> bool -> string -> Core.t
(** This version of [glob'] also recognizes the pattern \{..,..\}
@deprecated Prefer [glob ~expand_braces:true ~period].
*)

View file

@ -0,0 +1,78 @@
(* Result of a successful match. *)
type t =
{ s : string
; marks : Automata.mark_infos
; pmarks : Pmark.Set.t
; gpos : int array
; gcount : int
}
let offset t i =
if 2 * i + 1 >= Array.length t.marks then raise Not_found;
let m1 = t.marks.(2 * i) in
if m1 = -1 then raise Not_found;
let p1 = t.gpos.(m1) in
let p2 = t.gpos.(t.marks.(2 * i + 1)) in
(p1, p2)
let get t i =
let (p1, p2) = offset t i in
String.sub t.s p1 (p2 - p1)
let start subs i = fst (offset subs i)
let stop subs i = snd (offset subs i)
let test t i =
if 2 * i >= Array.length t.marks then
false
else
let idx = t.marks.(2 * i) in
idx <> -1
let get_opt t i =
if test t i
then Some (get t i)
else None
let dummy_offset = (-1, -1)
let all_offset t =
let res = Array.make t.gcount dummy_offset in
for i = 0 to Array.length t.marks / 2 - 1 do
let m1 = t.marks.(2 * i) in
if m1 <> -1 then begin
let p1 = t.gpos.(m1) in
let p2 = t.gpos.(t.marks.(2 * i + 1)) in
res.(i) <- (p1, p2)
end
done;
res
let dummy_string = ""
let all t =
let res = Array.make t.gcount dummy_string in
for i = 0 to Array.length t.marks / 2 - 1 do
let m1 = t.marks.(2 * i) in
if m1 <> -1 then begin
let p1 = t.gpos.(m1) in
let p2 = t.gpos.(t.marks.(2 * i + 1)) in
res.(i) <- String.sub t.s p1 (p2 - p1)
end
done;
res
let pp fmt t =
let matches =
let offsets = all_offset t in
let strs = all t in
Array.to_list (
Array.init (Array.length strs) (fun i -> strs.(i), offsets.(i))
) in
let open Fmt in
let pp_match fmt (str, (start, stop)) =
fprintf fmt "@[(%s (%d %d))@]" str start stop in
sexp fmt "Group" (list pp_match) matches
let nb_groups t = t.gcount

View file

@ -0,0 +1,55 @@
(* Result of a successful match. *)
type t =
{ s : string
(* Input string. Matched strings are substrings of s *)
; marks : Automata.mark_infos
(* Mapping from group indices to positions in gpos. group i has positions 2*i
- 1, 2*i + 1 in gpos. If the group wasn't matched, then its corresponding
values in marks will be -1,-1 *)
; pmarks : Pmark.Set.t
(* Marks positions. i.e. those marks created with Re.marks *)
; gpos : int array
(* Group positions. Adjacent elements are (start, stop) of group match.
indexed by the values in marks. So group i in an re would be the substring:
start = t.gpos.(marks.(2*i)) - 1
stop = t.gpos.(marks.(2*i + 1)) - 1 *)
; gcount : int
(* Number of groups the regular expression contains. Matched or not *)
}
(** Information about groups in a match. *)
val get : t -> int -> string
(** Raise [Not_found] if the group did not match *)
val get_opt : t -> int -> string option
(** Similar to {!get}, but returns an option instead of using an exception. *)
val offset : t -> int -> int * int
(** Raise [Not_found] if the group did not match *)
val start : t -> int -> int
(** Return the start of the match. Raise [Not_found] if the group did not match. *)
val stop : t -> int -> int
(** Return the end of the match. Raise [Not_found] if the group did not match. *)
val all : t -> string array
(** Return the empty string for each group which did not match *)
val all_offset : t -> (int * int) array
(** Return [(-1,-1)] for each group which did not match *)
val test : t -> int -> bool
(** Test whether a group matched *)
val nb_groups : t -> int
(** Returns the total number of groups defined - matched or not.
This function is experimental. *)
val pp : Format.formatter -> t -> unit

View file

@ -0,0 +1,140 @@
module Re = Core
exception Parse_error = Perl.Parse_error
exception Not_supported = Perl.Not_supported
type regexp = Re.re
type flag = [ `CASELESS | `MULTILINE | `ANCHORED | `DOTALL ]
type split_result =
| Text of string
| Delim of string
| Group of int * string
| NoGroup
type groups = Core.Group.t
let re ?(flags = []) pat =
let opts = List.map (function
| `CASELESS -> `Caseless
| `MULTILINE -> `Multiline
| `ANCHORED -> `Anchored
| `DOTALL -> `Dotall
) flags in
Perl.re ~opts pat
let regexp ?flags pat = Re.compile (re ?flags pat)
let extract ~rex s =
Re.Group.all (Re.exec rex s)
let exec ~rex ?pos s =
Re.exec rex ?pos s
let get_substring s i =
Re.Group.get s i
let names rex =
Re.group_names rex
|> List.map fst
|> Array.of_list
let get_named_substring rex name s =
let rec loop = function
| [] -> raise Not_found
| (n, i) :: rem when n = name ->
begin
try get_substring s i
with Not_found -> loop rem
end
| _ :: rem -> loop rem
in
loop (Re.group_names rex)
let get_substring_ofs s i =
Re.Group.offset s i
let pmatch ~rex s =
Re.execp rex s
let substitute ~rex ~subst str =
let b = Buffer.create 1024 in
let rec loop pos =
if pos >= String.length str then
Buffer.contents b
else if Re.execp ~pos rex str then (
let ss = Re.exec ~pos rex str in
let start, fin = Re.Group.offset ss 0 in
let pat = Re.Group.get ss 0 in
Buffer.add_substring b str pos (start - pos);
Buffer.add_string b (subst pat);
loop fin
) else (
Buffer.add_substring b str pos (String.length str - pos);
loop (String.length str)
)
in
loop 0
let split ~rex str =
let rec loop accu pos =
if pos >= String.length str then
List.rev accu
else if Re.execp ~pos rex str then (
let ss = Re.exec ~pos rex str in
let start, fin = Re.Group.offset ss 0 in
let s = String.sub str pos (start - pos) in
loop (s :: accu) fin
) else (
let s = String.sub str pos (String.length str - pos) in
loop (s :: accu) (String.length str)
) in
loop [] 0
(* From PCRE *)
let string_unsafe_sub s ofs len =
let r = Bytes.create len in
Bytes.unsafe_blit s ofs r 0 len;
Bytes.unsafe_to_string r
let quote s =
let len = String.length s in
let buf = Bytes.create (len lsl 1) in
let pos = ref 0 in
for i = 0 to len - 1 do
match String.unsafe_get s i with
| '\\' | '^' | '$' | '.' | '[' | '|'
| '(' | ')' | '?' | '*' | '+' | '{' as c ->
Bytes.unsafe_set buf !pos '\\';
incr pos;
Bytes.unsafe_set buf !pos c; incr pos
| c -> Bytes.unsafe_set buf !pos c; incr pos
done;
string_unsafe_sub buf 0 !pos
let full_split ?(max=0) ~rex s =
if String.length s = 0 then []
else if max = 1 then [Text s]
else
let results = Re.split_full rex s in
let matches =
List.map (function
| `Text s -> [Text s]
| `Delim d ->
let matches = Re.Group.all_offset d in
let delim = Re.Group.get d 0 in
(Delim delim)::(
let l = ref [] in
for i = 1 to Array.length matches - 1 do
l :=
(if matches.(i) = (-1, -1)
then NoGroup
else Group (i, Re.Group.get d i))
::(!l)
done;
List.rev !l)) results in
List.concat matches
type substrings = Group.t

View file

@ -0,0 +1,54 @@
exception Parse_error
exception Not_supported
type regexp = Core.re
type flag = [ `CASELESS | `MULTILINE | `ANCHORED | `DOTALL ]
type groups = Core.Group.t
(** Result of a {!Pcre.full_split} *)
type split_result =
| Text of string (** Text part of splitted string *)
| Delim of string (** Delimiter part of splitted string *)
| Group of int * string (** Subgroup of matched delimiter (subgroup_nr, subgroup_str) *)
| NoGroup (** Unmatched subgroup *)
val re : ?flags:(flag list) -> string -> Core.t
(** [re ~flags s] creates the regexp [s] using the pcre syntax. *)
val regexp : ?flags:(flag list) -> string -> regexp
(** [re ~flags s] compiles the regexp [s] using the pcre syntax. *)
val extract : rex:regexp -> string -> string array
(** [extract ~rex s] executes [rex] on [s] and returns the matching groups. *)
val exec : rex:regexp -> ?pos:int -> string -> groups
(** Equivalent to {!Core.exec}. *)
val get_substring : groups -> int -> string
(** Equivalent to {!Core.Group.get}. *)
val names : regexp -> string array
(** Return the names of named groups. *)
val get_named_substring : regexp -> string -> groups -> string
(** Return the first matched named group, or raise [Not_found]. *)
val get_substring_ofs : groups -> int -> int * int
(** Equivalent to {!Core.Group.offset}. *)
val pmatch : rex:regexp -> string -> bool
(** Equivalent to {!Core.execp}. *)
val substitute : rex:Core.re -> subst:(string -> string) -> string -> string
val full_split : ?max:int -> rex:regexp -> string -> split_result list
val split : rex:regexp -> string -> string list
val quote : string -> string
(** {2 Deprecated} *)
type substrings = Group.t

View file

@ -0,0 +1,316 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
module Re = Core
exception Parse_error
exception Not_supported
let posix_class_of_string = function
| "alpha" -> Re.alpha
| "alnum" -> Re.alnum
| "ascii" -> Re.ascii
| "blank" -> Re.blank
| "cntrl" -> Re.cntrl
| "digit" -> Re.digit
| "lower" -> Re.lower
| "print" -> Re.print
| "space" -> Re.space
| "upper" -> Re.upper
| "word" -> Re.wordc
| "punct" -> Re.punct
| "graph" -> Re.graph
| "xdigit" -> Re.xdigit
| class_ -> invalid_arg ("Invalid pcre class: " ^ class_)
let posix_class_strings =
[ "alpha" ; "alnum" ; "ascii"
; "blank" ; "cntrl" ; "digit"
; "lower" ; "print" ; "space"
; "upper" ; "word" ; "punct"
; "graph" ; "xdigit" ]
let parse multiline dollar_endonly dotall ungreedy s =
let i = ref 0 in
let l = String.length s in
let eos () = !i = l in
let test c = not (eos ()) && s.[!i] = c in
let accept c = let r = test c in if r then incr i; r in
let accept_s s' =
let len = String.length s' in
try
for j = 0 to len - 1 do
try if s'.[j] <> s.[!i + j] then raise Exit
with _ -> raise Exit
done;
i := !i + len;
true
with Exit -> false in
let get () = let r = s.[!i] in incr i; r in
let unget () = decr i in
let greedy_mod r =
let gr = accept '?' in
let gr = if ungreedy then not gr else gr in
if gr then Re.non_greedy r else Re.greedy r
in
let rec regexp () = regexp' (branch ())
and regexp' left =
if accept '|' then regexp' (Re.alt [left; branch ()]) else left
and branch () = branch' []
and branch' left =
if eos () || test '|' || test ')' then Re.seq (List.rev left)
else branch' (piece () :: left)
and piece () =
let r = atom () in
if accept '*' then greedy_mod (Re.rep r) else
if accept '+' then greedy_mod (Re.rep1 r) else
if accept '?' then greedy_mod (Re.opt r) else
if accept '{' then
match integer () with
Some i ->
let j = if accept ',' then integer () else Some i in
if not (accept '}') then raise Parse_error;
begin match j with
Some j when j < i -> raise Parse_error | _ -> ()
end;
greedy_mod (Re.repn r i j)
| None ->
unget (); r
else
r
and atom () =
if accept '.' then begin
if dotall then Re.any else Re.notnl
end else if accept '(' then begin
if accept '?' then begin
if accept ':' then begin
let r = regexp () in
if not (accept ')') then raise Parse_error;
r
end else if accept '#' then begin
comment ()
end else if accept '<' then begin
let name = name () in
let r = regexp () in
if not (accept ')') then raise Parse_error;
Re.group ~name r
end else
raise Parse_error
end else begin
let r = regexp () in
if not (accept ')') then raise Parse_error;
Re.group r
end
end else
if accept '^' then begin
if multiline then Re.bol else Re.bos
end else if accept '$' then begin
if multiline then Re.eol else if dollar_endonly then Re.leol else Re.eos
end else if accept '[' then begin
if accept '^' then
Re.compl (bracket [])
else
Re.alt (bracket [])
end else if accept '\\' then begin
(* XXX
- Back-references
- \cx (control-x), \ddd
*)
if eos () then raise Parse_error;
match get () with
'w' ->
Re.alt [Re.alnum; Re.char '_']
| 'W' ->
Re.compl [Re.alnum; Re.char '_']
| 's' ->
Re.space
| 'S' ->
Re.compl [Re.space]
| 'd' ->
Re.digit
| 'D' ->
Re.compl [Re.digit]
| 'b' ->
Re.alt [Re.bow; Re.eow]
| 'B' ->
Re.not_boundary
| 'A' ->
Re.bos
| 'Z' ->
Re.leol
| 'z' ->
Re.eos
| 'G' ->
Re.start
| 'e' ->
Re.char '\x1b'
| 'f' ->
Re.char '\x0c'
| 'n' ->
Re.char '\n'
| 'r' ->
Re.char '\r'
| 't' ->
Re.char '\t'
| 'x' ->
let c1 = hexdigit () in
let c2 = hexdigit () in
let code = c1 * 16 + c2 in
Re.char (char_of_int code)
| 'a'..'z' | 'A'..'Z' ->
raise Parse_error
| '0'..'9' ->
raise Not_supported
| c ->
Re.char c
end else begin
if eos () then raise Parse_error;
match get () with
'*' | '+' | '?' | '{' | '\\' -> raise Parse_error
| c -> Re.char c
end
and hexdigit () =
if eos () then raise Parse_error;
match get () with
'0'..'9' as d -> Char.code d - Char.code '0'
| 'a'..'f' as d -> Char.code d - Char.code 'a' + 10
| 'A'..'F' as d -> Char.code d - Char.code 'A' + 10
| _ -> raise Parse_error
and integer () =
if eos () then None else
match get () with
'0'..'9' as d -> integer' (Char.code d - Char.code '0')
| _ -> unget (); None
and integer' i =
if eos () then Some i else
match get () with
'0'..'9' as d ->
let i' = 10 * i + (Char.code d - Char.code '0') in
if i' < i then raise Parse_error;
integer' i'
| _ ->
unget (); Some i
and name () =
if eos () then raise Parse_error else
match get () with
('_' | 'a'..'z' | 'A'..'Z') as c ->
let b = Buffer.create 32 in
Buffer.add_char b c;
name' b
| _ -> raise Parse_error
and name' b =
if eos () then raise Parse_error else
match get () with
('_' | 'a'..'z' | 'A'..'Z' | '0'..'9') as c ->
Buffer.add_char b c;
name' b
| '>' -> Buffer.contents b
| _ -> raise Parse_error
and bracket s =
if s <> [] && accept ']' then s else begin
match char () with
| `Char c ->
if accept '-' then begin
if accept ']' then Re.char c :: Re.char '-' :: s else begin
match char () with
`Char c' ->
bracket (Re.rg c c' :: s)
| `Set st' ->
bracket (Re.char c :: Re.char '-' :: st' :: s)
end
end else
bracket (Re.char c :: s)
| `Set st -> bracket (st :: s)
end
and char () =
if eos () then raise Parse_error;
let c = get () in
if c = '[' then begin
if accept '=' then raise Not_supported;
if accept ':' then
let compl = accept '^' in
let cls =
try List.find accept_s posix_class_strings
with Not_found -> raise Parse_error in
if not (accept_s ":]") then raise Parse_error;
let re =
let posix_class = posix_class_of_string cls in
if compl then Re.compl [posix_class] else posix_class in
`Set (re)
else if accept '.' then begin
if eos () then raise Parse_error;
let c = get () in
if not (accept '.') then raise Not_supported;
if not (accept ']') then raise Parse_error;
`Char c
end else
`Char c
end else if c = '\\' then begin
if eos () then raise Parse_error;
let c = get () in
(* XXX
\127, ...
*)
match c with
'b' -> `Char '\008'
| 'n' -> `Char '\n' (*XXX*)
| 'r' -> `Char '\r' (*XXX*)
| 't' -> `Char '\t' (*XXX*)
| 'w' -> `Set (Re.alt [Re.alnum; Re.char '_'])
| 'W' -> `Set (Re.compl [Re.alnum; Re.char '_'])
| 's' -> `Set (Re.space)
| 'S' -> `Set (Re.compl [Re.space])
| 'd' -> `Set (Re.digit)
| 'D' -> `Set (Re.compl [Re.digit])
| 'a'..'z' | 'A'..'Z' ->
raise Parse_error
| '0'..'9' ->
raise Not_supported
| _ ->
`Char c
end else
`Char c
and comment () =
if eos () then raise Parse_error;
if accept ')' then Re.epsilon else begin incr i; comment () end
in
let res = regexp () in
if not (eos ()) then raise Parse_error;
res
type opt =
[ `Ungreedy | `Dotall | `Dollar_endonly
| `Multiline | `Anchored | `Caseless ]
let re ?(opts = []) s =
let r =
parse
(List.memq `Multiline opts) (List.memq `Dollar_endonly opts)
(List.memq `Dotall opts) (List.memq `Ungreedy opts)
s
in
let r = if List.memq `Anchored opts then Re.seq [Re.start; r] else r in
let r = if List.memq `Caseless opts then Re.no_case r else r in
r
let compile = Re.compile
let compile_pat ?(opts = []) s = compile (re ~opts s)

View file

@ -0,0 +1,41 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(** Perl-style regular expressions *)
exception Parse_error
exception Not_supported
(** Errors that can be raised during the parsing of the regular expression *)
type opt =
[ `Ungreedy | `Dotall | `Dollar_endonly
| `Multiline | `Anchored | `Caseless ]
val re : ?opts:opt list -> string -> Core.t
(** Parsing of a Perl-style regular expression *)
val compile : Core.t -> Core.re
(** (Same as [Re.compile]) *)
val compile_pat : ?opts:opt list -> string -> Core.re
(** Regular expression compilation *)

View file

@ -0,0 +1,13 @@
module Pmark = struct
type t = int
let equal (x : int) (y : int) = x = y
let compare (x : int) (y : int) = compare x y
let r = ref 0
let gen () = incr r ; !r
let pp = Format.pp_print_int
end
include Pmark
module Set = Set.Make(Pmark)

View file

@ -0,0 +1,8 @@
type t = private int
val equal : t -> t -> bool
val compare : t -> t -> int
val gen : unit -> t
val pp : Format.formatter -> t -> unit
module Set : Set.S with type elt = t

View file

@ -0,0 +1,156 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(*
What we could (should?) do:
- a* ==> longest ((shortest (no_group a)* ), a | ()) (!!!)
- abc understood as (ab)c
- "((a?)|b)" against "ab" should not bind the first subpattern to anything
Note that it should be possible to handle "(((ab)c)d)e" efficiently
*)
module Re = Core
exception Parse_error
exception Not_supported
let parse newline s =
let i = ref 0 in
let l = String.length s in
let eos () = !i = l in
let test c = not (eos ()) && s.[!i] = c in
let accept c = let r = test c in if r then incr i; r in
let get () = let r = s.[!i] in incr i; r in
let unget () = decr i in
let rec regexp () = regexp' (branch ())
and regexp' left =
if accept '|' then regexp' (Re.alt [left; branch ()]) else left
and branch () = branch' []
and branch' left =
if eos () || test '|' || test ')' then Re.seq (List.rev left)
else branch' (piece () :: left)
and piece () =
let r = atom () in
if accept '*' then Re.rep (Re.nest r) else
if accept '+' then Re.rep1 (Re.nest r) else
if accept '?' then Re.opt r else
if accept '{' then
match integer () with
Some i ->
let j = if accept ',' then integer () else Some i in
if not (accept '}') then raise Parse_error;
begin match j with
Some j when j < i -> raise Parse_error | _ -> ()
end;
Re.repn (Re.nest r) i j
| None ->
unget (); r
else
r
and atom () =
if accept '.' then begin
if newline then Re.notnl else Re.any
end else if accept '(' then begin
let r = regexp () in
if not (accept ')') then raise Parse_error;
Re.group r
end else
if accept '^' then begin
if newline then Re.bol else Re.bos
end else if accept '$' then begin
if newline then Re.eol else Re.eos
end else if accept '[' then begin
if accept '^' then
Re.diff (Re.compl (bracket [])) (Re.char '\n')
else
Re.alt (bracket [])
end else
if accept '\\' then begin
if eos () then raise Parse_error;
match get () with
'|' | '(' | ')' | '*' | '+' | '?'
| '[' | '.' | '^' | '$' | '{' | '\\' as c -> Re.char c
| _ -> raise Parse_error
end else begin
if eos () then raise Parse_error;
match get () with
'*' | '+' | '?' | '{' | '\\' -> raise Parse_error
| c -> Re.char c
end
and integer () =
if eos () then None else
match get () with
'0'..'9' as d -> integer' (Char.code d - Char.code '0')
| _ -> unget (); None
and integer' i =
if eos () then Some i else
match get () with
'0'..'9' as d ->
let i' = 10 * i + (Char.code d - Char.code '0') in
if i' < i then raise Parse_error;
integer' i'
| _ ->
unget (); Some i
and bracket s =
if s <> [] && accept ']' then s else begin
let c = char () in
if accept '-' then begin
if accept ']' then Re.char c :: Re.char '-' :: s else begin
let c' = char () in
bracket (Re.rg c c' :: s)
end
end else
bracket (Re.char c :: s)
end
and char () =
if eos () then raise Parse_error;
let c = get () in
if c = '[' then begin
if accept '=' then raise Not_supported
else if accept ':' then begin
raise Not_supported (*XXX*)
end else if accept '.' then begin
if eos () then raise Parse_error;
let c = get () in
if not (accept '.') then raise Not_supported;
if not (accept ']') then raise Parse_error;
c
end else
c
end else
c
in
let res = regexp () in
if not (eos ()) then raise Parse_error;
res
type opt = [`ICase | `NoSub | `Newline]
let re ?(opts = []) s =
let r = parse (List.memq `Newline opts) s in
let r = if List.memq `ICase opts then Re.no_case r else r in
let r = if List.memq `NoSub opts then Re.no_group r else r in
r
let compile re = Re.compile (Re.longest re)
let compile_pat ?(opts = []) s = compile (re ~opts s)

View file

@ -0,0 +1,98 @@
(*
RE - A regular expression library
Copyright (C) 2001 Jerome Vouillon
email: Jerome.Vouillon@pps.jussieu.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, with
linking exception; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*)
(**
References:
- {{: http://www.opengroup.org/onlinepubs/007908799/xbd/re.html} re}
- {{: http://www.opengroup.org/onlinepubs/007908799/xsh/regcomp.html} regcomp}
Example of how to use this module (to parse some IRC logs):
{[
type msg = {
time:string;
author:string;
content:string;
}
let re = Core.compile (Re_posix.re "([^:].*:[^:]*:[^:]{2})<.([^>]+)> (.+)$")
(* parse a line *)
let match_line line =
try
let substrings = Core.exec re line in
let groups = Core.get_all substrings in
(* groups can be obtained directly by index within [substrings] *)
Some {time=groups.(1); author=groups.(2); content=groups.(3)}
with Not_found ->
None (* regex didn't match *)
]}
*)
(* XXX Character classes *)
exception Parse_error
exception Not_supported
(** Errors that can be raised during the parsing of the regular expression *)
type opt = [`ICase | `NoSub | `Newline]
val re : ?opts:(opt list) -> string -> Core.t
(** Parsing of a Posix extended regular expression *)
val compile : Core.t -> Core.re
(** [compile r] is defined as [Core.compile (Core.longest r)] *)
val compile_pat : ?opts:(opt list) -> string -> Core.re
(** [compile_pat ?opts regex] compiles the Posix extended regular expression [regexp] *)
(*
Deviation from the standard / ambiguities in the standard
---------------------------------------------------------
We tested the behavior of the Linux library (glibc) and the Solaris
library.
(1) An expression [efg] should be parsed as [(ef)g].
All implementations parse it as [e(fg)].
(2) When matching the pattern "((a)|b)*" against the string "ab",
the sub-expression "((a)|b)" should match "b", and the
sub-expression "(a)" should not match anything.
In both implementation, the sub-expression "(a)" matches "a".
(3) When matching the pattern "(aa?)*" against the string "aaa", it is
not clear whether the final match of the sub-expression "(aa?)" is
the last "a" (all matches of the sub-expression are successively
maximized), or "aa" (the final match is maximized).
Both implementations implements the first case.
(4) When matching the pattern "((a?)|b)*" against the string "ab",
the sub-expression "((a?)|b)" should match the empty string at the
end of the string (it is better to match the empty string than to
match nothing).
In both implementations, this sub-expression matches "b".
(Strangely, in the Linux implementation, the sub-expression "(a?)"
correctly matches the empty string at the end of the string)
This library behaves the same way as the other libraries for all
points, except for (2) and (4) where it follows the standard.
The behavior of this library in theses four cases may change in future
releases.
*)

View file

@ -0,0 +1,8 @@
include Core
module Emacs = Emacs
module Glob = Glob
module Perl = Perl
module Pcre = Pcre
module Posix = Posix
module Str = Str

View file

@ -0,0 +1,297 @@
(***********************************************************************)
(* *)
(* Objective Caml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the GNU Library General Public License, with *)
(* linking exception. *)
(* *)
(***********************************************************************)
(* Modified by Jerome.Vouillon@pps.jussieu.fr for integration in RE *)
(* $Id: re_str.ml,v 1.3 2002/07/03 15:47:54 vouillon Exp $ *)
module Re = Core
type regexp =
{ re: Re.t
; mtch: Re.re Lazy.t
; srch: Re.re Lazy.t }
let compile_regexp s c =
let re = Emacs.re ~case:(not c) s in
{ re
; mtch = lazy (Re.compile (Re.seq [Re.start; re]))
; srch = lazy (Re.compile re) }
let state = ref None
let string_match re s p =
try
state := Some (Re.exec ~pos:p (Lazy.force re.mtch) s);
true
with Not_found ->
state := None;
false
let string_partial_match re s p =
match
Re.exec_partial ~pos:p (Lazy.force re.mtch) s
with
`Full -> string_match re s p
| `Partial -> true
| `Mismatch -> false
let search_forward re s p =
try
let res = Re.exec ~pos:p (Lazy.force re.srch) s in
state := Some res;
fst (Re.Group.offset res 0)
with Not_found ->
state := None;
raise Not_found
let rec search_backward re s p =
try
let res = Re.exec ~pos:p (Lazy.force re.mtch) s in
state := Some res;
p
with Not_found ->
state := None;
if p = 0 then
raise Not_found
else
search_backward re s (p - 1)
let valid_group n =
n >= 0 && n < 10 && (
match !state with
| None -> false
| Some m -> n < Re.Group.nb_groups m
)
let offset_group i =
match !state with
| Some m -> Re.Group.offset m i
| None -> raise Not_found
let group_len i =
try
let (b, e) = offset_group i in
e - b
with Not_found ->
0
let rec repl_length repl p q len =
if p < len then begin
if repl.[p] <> '\\' then
repl_length repl (p + 1) (q + 1) len
else begin
let p = p + 1 in
if p = len then failwith "Str.replace: illegal backslash sequence";
let q =
match repl.[p] with
| '\\' -> q + 1
| '0' .. '9' as c -> q + group_len (Char.code c - Char.code '0')
| _ -> q + 2 in
repl_length repl (p + 1) q len
end
end else
q
let rec replace orig repl p res q len =
if p < len then begin
let c = repl.[p] in
if c <> '\\' then begin
Bytes.set res q c;
replace orig repl (p + 1) res (q + 1) len
end else begin
match repl.[p + 1] with
'\\' ->
Bytes.set res q '\\';
replace orig repl (p + 2) res (q + 1) len
| '0' .. '9' as c ->
let d =
try
let (b, e) = offset_group (Char.code c - Char.code '0') in
let d = e - b in
if d > 0 then String.blit orig b res q d;
d
with Not_found ->
0
in
replace orig repl (p + 2) res (q + d) len
| c ->
Bytes.set res q '\\';
Bytes.set res (q + 1) c;
replace orig repl (p + 2) res (q + 2) len
end
end
let replacement_text repl orig =
let len = String.length repl in
let res = Bytes.create (repl_length repl 0 0 len) in
replace orig repl 0 res 0 (String.length repl);
Bytes.unsafe_to_string res
let quote s =
let len = String.length s in
let buf = Buffer.create (2 * len) in
for i = 0 to len - 1 do
match s.[i] with
'[' | ']' | '*' | '.' | '\\' | '?' | '+' | '^' | '$' as c ->
Buffer.add_char buf '\\';
Buffer.add_char buf c
| c -> Buffer.add_char buf c
done;
Buffer.contents buf
let string_before s n = String.sub s 0 n
let string_after s n = String.sub s n (String.length s - n)
let first_chars s n = String.sub s 0 n
let last_chars s n = String.sub s (String.length s - n) n
let regexp e = compile_regexp e false
let regexp_case_fold e = compile_regexp e true
let regexp_string s = compile_regexp (quote s) false
let regexp_string_case_fold s = compile_regexp (quote s) true
let group_beginning n =
if not (valid_group n) then invalid_arg "Str.group_beginning";
let pos = fst (offset_group n) in
if pos = -1 then
raise Not_found
else
pos
let group_end n =
if not (valid_group n) then invalid_arg "Str.group_end";
let pos = snd (offset_group n) in
if pos = -1 then
raise Not_found
else
pos
let matched_group n txt =
let (b, e) = offset_group n in
String.sub txt b (e - b)
let replace_matched repl matched = replacement_text repl matched
let match_beginning () = group_beginning 0
and match_end () = group_end 0
and matched_string txt = matched_group 0 txt
let substitute_first expr repl_fun text =
try
let pos = search_forward expr text 0 in
String.concat "" [string_before text pos;
repl_fun text;
string_after text (match_end ())]
with Not_found ->
text
let global_substitute expr repl_fun text =
let rec replace accu start last_was_empty =
let startpos = if last_was_empty then start + 1 else start in
if startpos > String.length text then
(string_after text start) :: accu
else
match search_forward expr text startpos with
| pos ->
let end_pos = match_end () in
let repl_text = repl_fun text in
replace (repl_text :: String.sub text start (pos-start) :: accu)
end_pos (end_pos = pos)
| exception Not_found -> (string_after text start) :: accu
in
String.concat "" (List.rev (replace [] 0 false))
let global_replace expr repl text =
global_substitute expr (replacement_text repl) text
and replace_first expr repl text =
substitute_first expr (replacement_text repl) text
let search_forward_progress re s p =
let pos = search_forward re s p in
if match_end () > p then
pos
else if p < String.length s then
search_forward re s (p + 1)
else
raise Not_found
let bounded_split expr text num =
let start =
if string_match expr text 0 then match_end () else 0 in
let rec split accu start n =
if start >= String.length text then
accu
else if n = 1 then
(string_after text start) :: accu
else
try
let pos = search_forward_progress expr text start in
split ((String.sub text start (pos-start)) :: accu)
(match_end ()) (n - 1)
with Not_found ->
(string_after text start) :: accu in
List.rev (split [] start num)
let split expr text = bounded_split expr text 0
let bounded_split_delim expr text num =
let rec split accu start n =
if start > String.length text then
accu
else if n = 1 then
(string_after text start) :: accu
else
try
let pos = search_forward_progress expr text start in
split (String.sub text start (pos-start) :: accu)
(match_end ()) (n - 1)
with Not_found ->
(string_after text start) :: accu in
if text = "" then
[]
else
List.rev (split [] 0 num)
let split_delim expr text = bounded_split_delim expr text 0
type split_result = Text of string | Delim of string
let bounded_full_split expr text num =
let rec split accu start n =
if start >= String.length text then
accu
else if n = 1 then
Text (string_after text start) :: accu
else
try
let pos = search_forward_progress expr text start in
let s = matched_string text in
if pos > start then
split (Delim (s) ::
Text (String.sub text start (pos - start)) ::
accu)
(match_end ()) (n - 1)
else
split (Delim (s) :: accu)
(match_end ()) (n - 1)
with Not_found ->
Text (string_after text start) :: accu in
List.rev (split [] 0 num)
let full_split expr text = bounded_full_split expr text 0

View file

@ -0,0 +1,218 @@
(***********************************************************************)
(* *)
(* Objective Caml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the GNU Library General Public License, with *)
(* linking exception. *)
(* *)
(***********************************************************************)
(* $Id: re_str.mli,v 1.1 2002/01/16 14:16:04 vouillon Exp $ *)
(** Module [Str]: regular expressions and high-level string processing *)
(** {2 Regular expressions} *)
type regexp
(** The type of compiled regular expressions. *)
val regexp: string -> regexp
(** Compile a regular expression. The syntax for regular expressions
is the same as in Gnu Emacs. The special characters are
[$^.*+?[]]. The following constructs are recognized:
- [. ] matches any character except newline
- [* ] (postfix) matches the previous expression zero, one or
several times
- [+ ] (postfix) matches the previous expression one or
several times
- [? ] (postfix) matches the previous expression once or
not at all
- [[..] ] character set; ranges are denoted with [-], as in [[a-z]];
an initial [^], as in [[^0-9]], complements the set
- [^ ] matches at beginning of line
- [$ ] matches at end of line
- [\| ] (infix) alternative between two expressions
- [\(..\)] grouping and naming of the enclosed expression
- [\1 ] the text matched by the first [\(...\)] expression
([\2] for the second expression, etc)
- [\b ] matches word boundaries
- [\ ] quotes special characters. *)
val regexp_case_fold: string -> regexp
(** Same as [regexp], but the compiled expression will match text
in a case-insensitive way: uppercase and lowercase letters will
be considered equivalent. *)
val quote: string -> string
(** [Str.quote s] returns a regexp string that matches exactly
[s] and nothing else. *)
val regexp_string: string -> regexp
(** [Str.regexp_string s] returns a regular expression
that matches exactly [s] and nothing else. *)
val regexp_string_case_fold: string -> regexp
(** [Str.regexp_string_case_fold] is similar to [Str.regexp_string], but the regexp
matches in a case-insensitive way. *)
(** {2 String matching and searching} *)
val string_match: regexp -> string -> int -> bool
(** [string_match r s start] tests whether the characters in [s]
starting at position [start] match the regular expression [r].
The first character of a string has position [0], as usual. *)
val search_forward: regexp -> string -> int -> int
(** [search_forward r s start] searches the string [s] for a substring
matching the regular expression [r]. The search starts at position
[start] and proceeds towards the end of the string.
Return the position of the first character of the matched
substring, or raise [Not_found] if no substring matches. *)
val search_backward: regexp -> string -> int -> int
(** Same as [search_forward], but the search proceeds towards the
beginning of the string. *)
val string_partial_match: regexp -> string -> int -> bool
(** Similar to [string_match], but succeeds whenever the argument
string is a prefix of a string that matches. This includes
the case of a true complete match. *)
val matched_string: string -> string
(** [matched_string s] returns the substring of [s] that was matched
by the latest [string_match], [search_forward] or [search_backward].
The user must make sure that the parameter [s] is the same string
that was passed to the matching or searching function. *)
val match_beginning: unit -> int
(** [match_beginning ()] returns the position of the first character
of the substring that was matched by [string_match],
[search_forward] or [search_backward]. *)
val match_end: unit -> int
(** [match_end ()] returns the position of the character following the
last character of the substring that was matched by [string_match],
[search_forward] or [search_backward]. *)
val matched_group: int -> string -> string
(** [matched_group n s] returns the substring of [s] that was matched
by the [n]th group [\(...\)] of the regular expression during
the latest [string_match], [search_forward] or [search_backward].
The user must make sure that the parameter [s] is the same string
that was passed to the matching or searching function.
[matched_group n s] raises [Not_found] if the [n]th group
of the regular expression was not matched. This can happen
with groups inside alternatives [\|], options [?]
or repetitions [*]. For instance, the empty string will match
[\(a\)*], but [matched_group 1 ""] will raise [Not_found]
because the first group itself was not matched. *)
val group_beginning: int -> int
(** [group_beginning n] returns the position of the first character
of the substring that was matched by the [n]th group of the regular expression.
Raises [Not_found] if the [n]th group of the regular expression was not matched. *)
val group_end: int -> int
(** [group_end n] returns the position of the character following
the last character of the matched substring.
Raises [Not_found] if the [n]th group of the regular expression was not matched. *)
(** {2 Replacement} *)
val global_replace: regexp -> string -> string -> string
(** [global_replace regexp templ s] returns a string identical to [s],
except that all substrings of [s] that match [regexp] have been
replaced by [templ]. The replacement template [templ] can contain
[\1], [\2], etc; these sequences will be replaced by the text
matched by the corresponding group in the regular expression.
[\0] stands for the text matched by the whole regular expression. *)
val replace_first: regexp -> string -> string -> string
(** Same as [global_replace], except that only the first substring
matching the regular expression is replaced. *)
val global_substitute: regexp -> (string -> string) -> string -> string
(** [global_substitute regexp subst s] returns a string identical
to [s], except that all substrings of [s] that match [regexp]
have been replaced by the result of function [subst]. The
function [subst] is called once for each matching substring,
and receives [s] (the whole text) as argument. *)
val substitute_first: regexp -> (string -> string) -> string -> string
(** Same as [global_substitute], except that only the first substring
matching the regular expression is replaced. *)
val replace_matched : string -> string -> string
(** [replace_matched repl s] returns the replacement text [repl]
in which [\1], [\2], etc. have been replaced by the text
matched by the corresponding groups in the most recent matching
operation. [s] must be the same string that was matched during
this matching operation. *)
(** {2 Splitting} *)
val split: regexp -> string -> string list
(** [split r s] splits [s] into substrings, taking as delimiters
the substrings that match [r], and returns the list of substrings.
For instance, [split (regexp "[ \t]+") s] splits [s] into
blank-separated words. An occurrence of the delimiter at the
beginning and at the end of the string is ignored. *)
val bounded_split: regexp -> string -> int -> string list
(** Same as [split], but splits into at most [n] substrings,
where [n] is the extra integer parameter. *)
val split_delim: regexp -> string -> string list
(** Same as [split], but occurrences of the delimiter at the beginning
and at the end of the string are recognized and returned as empty strings
in the result.
For instance, [split_delim (regexp " ") " abc "] returns [[""; "abc"; ""]],
while [split] with the same arguments returns [["abc"]]. *)
val bounded_split_delim: regexp -> string -> int -> string list
(** Same as [bounded_split] and [split_delim], but occurrences of
the delimiter at the beginning and at the end of the string are recognized
and returned as empty strings in the result.
For instance, [split_delim (regexp " ") " abc "] returns [[""; "abc"; ""]],
while [split] with the same arguments returns [["abc"]]. *)
type split_result = Text of string | Delim of string
val full_split: regexp -> string -> split_result list
(** Same as [split_delim], but returns the delimiters
as well as the substrings contained between delimiters.
The former are tagged [Delim] in the result list;
the latter are tagged [Text].
For instance, [full_split (regexp "[{}]") "{ab}"] returns
[[Delim "{"; Text "ab"; Delim "}"]]. *)
val bounded_full_split: regexp -> string -> int -> split_result list
(** Same as [split_delim] and [bounded_split_delim], but returns
the delimiters as well as the substrings contained between delimiters.
The former are tagged [Delim] in the result list;
the latter are tagged [Text].
For instance, [full_split (regexp "[{}]") "{ab}"] returns
[[Delim "{"; Text "ab"; Delim "}"]]. *)
(** {2 Extracting substrings} *)
val string_before: string -> int -> string
(** [string_before s n] returns the substring of all characters of [s]
that precede position [n] (excluding the character at
position [n]). *)
val string_after: string -> int -> string
(** [string_after s n] returns the substring of all characters of [s]
that follow position [n] (including the character at
position [n]). *)
val first_chars: string -> int -> string
(** [first_chars s n] returns the first [n] characters of [s].
This is the same function as [string_before]. *)
val last_chars: string -> int -> string
(** [last_chars s n] returns the last [n] characters of [s]. *)