[project @ 2005-03-27 13:41:19 by panne]
[ghc-base.git] / Text / Regex.hs
1 -----------------------------------------------------------------------------
2 -- |
3 -- Module      :  Text.Regex
4 -- Copyright   :  (c) The University of Glasgow 2001
5 -- License     :  BSD-style (see the file libraries/base/LICENSE)
6 -- 
7 -- Maintainer  :  libraries@haskell.org
8 -- Stability   :  experimental
9 -- Portability :  portable
10 --
11 -- Regular expression matching.  Uses the POSIX regular expression
12 -- interface in "Text.Regex.Posix".
13 --
14 -----------------------------------------------------------------------------
15 module Text.Regex (
16     -- * Regular expressions
17     Regex,
18     mkRegex,
19     mkRegexWithOpts,
20     matchRegex,
21     matchRegexAll,
22     subRegex,
23     splitRegex
24   ) where
25
26 import Prelude
27 import qualified Text.Regex.Posix as RE
28 import Text.Regex.Posix ( Regex )
29 import System.IO.Unsafe
30
31 -- | Makes a regular expression with the default options (multi-line,
32 -- case-sensitive).  The syntax of regular expressions is
33 -- otherwise that of @egrep@ (i.e. POSIX \"extended\" regular
34 -- expressions).
35 mkRegex :: String -> Regex
36 mkRegex s = unsafePerformIO (RE.regcomp s RE.regExtended)
37
38 -- | Makes a regular expression, where the multi-line and
39 -- case-sensitive options can be changed from the default settings.
40 mkRegexWithOpts
41    :: String  -- ^ The regular expression to compile
42    -> Bool    -- ^ 'True' @\<=>@ @\'^\'@ and @\'$\'@ match the beginning and 
43               -- end of individual lines respectively, and @\'.\'@ does /not/
44               -- match the newline character.
45    -> Bool    -- ^ 'True' @\<=>@ matching is case-sensitive
46    -> Regex   -- ^ Returns: the compiled regular expression
47
48 mkRegexWithOpts s single_line case_sensitive
49    = unsafePerformIO (RE.regcomp s (RE.regExtended + newline + igcase))
50    where
51         newline | single_line = RE.regNewline
52                 | otherwise   = 0
53
54         igcase  | case_sensitive = 0 
55                 | otherwise      = RE.regIgnoreCase
56
57 -- | Match a regular expression against a string
58 matchRegex
59    :: Regex     -- ^ The regular expression
60    -> String    -- ^ The string to match against
61    -> Maybe [String]    -- ^ Returns: @'Just' strs@ if the match succeeded
62                         -- (and @strs@ is the list of subexpression matches),
63                         -- or 'Nothing' otherwise.
64 matchRegex p str = 
65   case (unsafePerformIO (RE.regexec p str)) of
66         Nothing -> Nothing
67         Just (before, match, after, sub_strs) -> Just sub_strs
68
69 -- | Match a regular expression against a string, returning more information
70 -- about the match.
71 matchRegexAll
72    :: Regex     -- ^ The regular expression
73    -> String    -- ^ The string to match against
74    -> Maybe ( String, String, String, [String] )
75                 -- ^ Returns: 'Nothing' if the match failed, or:
76                 -- 
77                 -- >  Just ( everything before match,
78                 -- >         portion matched,
79                 -- >         everything after the match,
80                 -- >         subexpression matches )
81
82 matchRegexAll p str = unsafePerformIO (RE.regexec p str)
83
84 {- | Replaces every occurance of the given regexp with the replacement string.
85
86 In the replacement string, @\"\\1\"@ refers to the first substring;
87 @\"\\2\"@ to the second, etc; and @\"\\0\"@ to the entire match.
88 @\"\\\\\\\\\"@ will insert a literal backslash.
89
90 -}
91 subRegex :: Regex                          -- ^ Search pattern
92       -> String                         -- ^ Input string
93       -> String                         -- ^ Replacement text
94       -> String                         -- ^ Output string
95 subRegex _ "" _ = ""
96 subRegex regexp inp repl =
97     let bre = mkRegex "\\\\(\\\\||[0-9]+)"
98         lookup _ [] _ = []
99         lookup [] _ _ = []
100         lookup match repl groups =
101             case matchRegexAll bre repl of
102                 Nothing -> repl
103                 Just (lead, _, trail, bgroups) ->
104                     let newval = if (head bgroups) == "\\"
105                                  then "\\"
106                                  else let index = (read (head bgroups)) - 1
107                                           in
108                                           if index == -1
109                                              then match
110                                              else groups !! index
111                         in
112                         lead ++ newval ++ lookup match trail groups
113         in
114         case matchRegexAll regexp inp of
115             Nothing -> inp
116             Just (lead, match, trail, groups) ->
117               lead ++ lookup match repl groups ++ (subRegex regexp trail repl)
118
119 {- | Splits a string based on a regular expression.  The regular expression
120 should identify one delimiter.
121 -}
122
123 splitRegex :: Regex -> String -> [String]
124 splitRegex _ [] = []
125 splitRegex delim str =
126     case matchRegexAll delim str of
127        Nothing -> [str]
128        Just (firstline, _, remainder, _) ->
129            if remainder == ""
130               then firstline : [] : []
131               else firstline : splitRegex delim remainder