{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
-----------------------------------------------------------------------------
-- |
-- Module : Text.Regex
-- Copyright : (c) Chris Kuklewicz 2006, derived from (c) The University of Glasgow 2001
-- License : BSD-style (see the file LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : experimental
-- Portability : non-portable (regex-base needs MPTC+FD)
--
-- Regular expression matching. Uses the POSIX regular expression
-- interface in "Text.Regex.Posix".
--
---------------------------------------------------------------------------
--
-- Modified by Chris Kuklewicz to be a thin layer over the regex-posix
-- package, and moved into a regex-compat package.
--
module Text.Regex (
-- * Regular expressions
Regex,
mkRegex,
mkRegexWithOpts,
matchRegex,
matchRegexAll,
subRegex,
splitRegex
) where
import Data.Bits((.|.))
import Text.Regex.Base(RegexMaker(makeRegexOpts),defaultExecOpt,RegexLike(matchOnceText),RegexContext(matchM))
import Text.Regex.Posix(Regex,compNewline,compIgnoreCase,compExtended)
-- | Makes a regular expression with the default options (multi-line,
-- case-sensitive). The syntax of regular expressions is
-- otherwise that of @egrep@ (i.e. POSIX \"extended\" regular
-- expressions).
mkRegex :: String -> Regex
mkRegex s = makeRegexOpts opt defaultExecOpt s
where opt = compExtended .|. compNewline
-- | Makes a regular expression, where the multi-line and
-- case-sensitive options can be changed from the default settings.
mkRegexWithOpts
:: String -- ^ The regular expression to compile
-> Bool -- ^ 'True' @\<=>@ @\'^\'@ and @\'$\'@ match the beginning and
-- end of individual lines respectively, and @\'.\'@ does /not/
-- match the newline character.
-> Bool -- ^ 'True' @\<=>@ matching is case-sensitive
-> Regex -- ^ Returns: the compiled regular expression
mkRegexWithOpts s single_line case_sensitive
= let opt = (if single_line then (compNewline .|.) else id) .
(if case_sensitive then id else (compIgnoreCase .|.)) $
compExtended
in makeRegexOpts opt defaultExecOpt s
-- | Match a regular expression against a string
matchRegex
:: Regex -- ^ The regular expression
-> String -- ^ The string to match against
-> Maybe [String] -- ^ Returns: @'Just' strs@ if the match succeeded
-- (and @strs@ is the list of subexpression matches),
-- or 'Nothing' otherwise.
matchRegex p str = fmap (\(_,_,_,str) -> str) (matchRegexAll p str)
-- | Match a regular expression against a string, returning more information
-- about the match.
matchRegexAll
:: Regex -- ^ The regular expression
-> String -- ^ The string to match against
-> Maybe ( String, String, String, [String] )
-- ^ Returns: 'Nothing' if the match failed, or:
--
-- > Just ( everything before match,
-- > portion matched,
-- > everything after the match,
-- > subexpression matches )
matchRegexAll p str = matchM p str
{- | Replaces every occurance of the given regexp with the replacement string.
In the replacement string, @\"\\1\"@ refers to the first substring;
@\"\\2\"@ to the second, etc; and @\"\\0\"@ to the entire match.
@\"\\\\\\\\\"@ will insert a literal backslash.
This does not advance if the regex matches an empty string. This
misfeature is here to match the behavior of the the original
Text.Regex API.
-}
subRegex :: Regex -- ^ Search pattern
-> String -- ^ Input string
-> String -- ^ Replacement text
-> String -- ^ Output string
subRegex _ "" _ = ""
subRegex regexp inp repl =
let -- bre matches a backslash then capture either a backslash or some digits
bre = mkRegex "\\\\(\\\\|[0-9]+)"
lookup _ [] _ = []
lookup [] _ _ = []
lookup match repl groups =
case matchRegexAll bre repl of
Nothing -> repl
Just (lead, _, trail, bgroups) ->
let newval =
if (head bgroups) == "\\"
then "\\"
else let index :: Int
index = (read (head bgroups)) - 1
in if index == -1
then match
else groups !! index
in lead ++ newval ++ lookup match trail groups
in case matchRegexAll regexp inp of
Nothing -> inp
Just (lead, match, trail, groups) ->
lead ++ lookup match repl groups ++ (subRegex regexp trail repl)
{- | Splits a string based on a regular expression. The regular expression
should identify one delimiter.
This does not advance and produces an infinite list of [] if the regex
matches an empty string. This misfeature is here to match the
behavior of the the original Text.Regex API.
-}
splitRegex :: Regex -> String -> [String]
splitRegex _ [] = []
splitRegex delim strIn = loop strIn where
loop str = case matchOnceText delim str of
Nothing -> [str]
Just (firstline, _, remainder) ->
if null remainder
then [firstline,""]
else firstline : loop remainder