337 lines
10 KiB
OCaml
337 lines
10 KiB
OCaml
(*
|
|
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 = Parse_buffer.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 buf = Parse_buffer.create s in
|
|
let eos () = Parse_buffer.eos buf in
|
|
let read c = Parse_buffer.accept buf c in
|
|
let char () =
|
|
ignore (read '\\' : bool);
|
|
if eos () then raise Parse_error;
|
|
Parse_buffer.get buf
|
|
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 acc =
|
|
if double_asterisk && Parse_buffer.accept_s buf "/**"
|
|
then ManyMany :: (if eos () then Exactly '/' :: acc else acc)
|
|
else if read '*'
|
|
then (if double_asterisk && read '*' then ManyMany else Many) :: acc
|
|
else if read '?'
|
|
then One :: acc
|
|
else if not (read '[')
|
|
then Exactly (char ()) :: acc
|
|
else if read '^' || read '!'
|
|
then Any_but (enclosed ()) :: acc
|
|
else Any_of (enclosed ()) :: acc
|
|
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 (
|
|
if inner then raise Parse_error;
|
|
mul beg [ String.sub str s (i - s) ], i)
|
|
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 State.append state (Re.rep (one ~explicit_slash ~slashes ~explicit_period))
|
|
else if not explicit_slash
|
|
then
|
|
(* 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)
|
|
]))
|
|
else (
|
|
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)
|
|
;;
|
|
|
|
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_result
|
|
?anchored
|
|
?pathname
|
|
?match_backslashes
|
|
?period
|
|
?expand_braces
|
|
?double_asterisk
|
|
s
|
|
=
|
|
match
|
|
glob ?anchored ?pathname ?match_backslashes ?period ?expand_braces ?double_asterisk s
|
|
with
|
|
| re -> Ok re
|
|
| exception Parse_error -> Error `Parse_error
|
|
;;
|
|
|
|
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
|