[project @ 2002-09-25 23:01:11 by ross]
[ghc-base.git] / Text / Regex / Posix.hsc
1 -----------------------------------------------------------------------------
2 -- |
3 -- Module      :  Text.Regex.Posix
4 -- Copyright   :  (c) The University of Glasgow 2002
5 -- License     :  BSD-style (see the file libraries/base/LICENSE)
6 -- 
7 -- Maintainer  :  libraries@haskell.org
8 -- Stability   :  experimental
9 -- Portability :  non-portable (needs POSIX regexps)
10 --
11 -- Interface to the POSIX regular expression library.
12 --
13 -----------------------------------------------------------------------------
14
15 -- ToDo: should have an interface using PackedStrings.
16
17 module Text.Regex.Posix (
18         -- * The @Regex@ type
19         Regex,          -- abstract
20
21         -- * Compiling a regular expression
22         regcomp,        -- :: String -> Int -> IO Regex
23
24         -- ** Flags for regcomp
25         regExtended,    -- (flag to regcomp) use extended regex syntax
26         regIgnoreCase,  -- (flag to regcomp) ignore case when matching
27         regNewline,     -- (flag to regcomp) '.' doesn't match newline
28
29         -- * Matching a regular expression
30         regexec,        -- :: Regex                  -- pattern
31                         -- -> String                 -- string to match
32                         -- -> IO (Maybe (String,     -- everything before match
33                         --               String,     -- matched portion
34                         --               String,     -- everything after match
35                         --               [String]))  -- subexpression matches
36
37   ) where
38
39 #include <sys/types.h>
40 #include "regex.h"
41
42 import Prelude
43
44 import Foreign
45 import Foreign.C
46
47 -- | A compiled regular expression
48 newtype Regex = Regex (ForeignPtr CRegex)
49
50 -- -----------------------------------------------------------------------------
51 -- regcomp
52
53 -- | Compiles a regular expression
54 regcomp
55   :: String     -- ^ The regular expression to compile
56   -> Int        -- ^ Flags (summed together)
57   -> IO Regex   -- ^ Returns: the compiled regular expression
58 regcomp pattern flags = do
59 #ifdef __HUGS__
60   regex_fptr <- mallocForeignPtrBytes (#const sizeof(regex_t))
61 #else
62   regex_ptr <- mallocBytes (#const sizeof(regex_t))
63   regex_fptr <- newForeignPtr regex_ptr (regfree regex_ptr)
64 #endif /* __HUGS__ */
65   r <- withCString pattern $ \cstr ->
66          withForeignPtr regex_fptr $ \p ->
67            c_regcomp p cstr (fromIntegral flags)
68 #ifdef __HUGS__
69   addForeignPtrFinalizer regex_fptr ptr_regfree
70 #endif
71   if (r == 0)
72      then return (Regex regex_fptr)
73      else error "Text.Regex.Posix.regcomp: error in pattern" -- ToDo
74
75 #ifndef __HUGS__
76 regfree :: Ptr CRegex -> IO ()
77 regfree p_regex = do
78   c_regfree p_regex
79   free p_regex
80 #endif /* __HUGS__ */
81
82 -- -----------------------------------------------------------------------------
83 -- regexec
84
85 -- | Matches a regular expression against a string
86 regexec :: Regex                        -- ^ Compiled regular expression
87         -> String                       -- ^ String to match against
88         -> IO (Maybe (String, String, String, [String]))
89                 -- ^ Returns: 'Nothing' if the regex did not match the
90                 -- string, or:
91                 --
92                 -- @
93                 --   'Just' (everything before match,
94                 --         matched portion,
95                 --         everything after match,
96                 --         subexpression matches)
97                 -- @
98
99 regexec (Regex regex_fptr) str = do
100   withCString str $ \cstr -> do
101     withForeignPtr regex_fptr $ \regex_ptr -> do
102       nsub <- (#peek regex_t, re_nsub) regex_ptr
103       let nsub_int = fromIntegral (nsub :: CSize)
104       allocaBytes ((1 + nsub_int) * (#const sizeof(regmatch_t))) $ \p_match -> do
105                 -- add one because index zero covers the whole match
106         r <- c_regexec regex_ptr cstr (1 + nsub) p_match 0{-no flags for now-}
107
108         if (r /= 0) then return Nothing else do 
109
110         (before,match,after) <- matched_parts str p_match
111
112         sub_strs <- 
113           mapM (unpack str) $ take nsub_int $ tail $
114              iterate (`plusPtr` (#const sizeof(regmatch_t))) p_match
115
116         return (Just (before, match, after, sub_strs))
117
118 matched_parts :: String -> Ptr CRegMatch -> IO (String, String, String)
119 matched_parts string p_match = do
120   start <- (#peek regmatch_t, rm_so) p_match :: IO CInt
121   end   <- (#peek regmatch_t, rm_eo) p_match :: IO CInt
122   let s = fromIntegral start; e = fromIntegral end
123   return ( take (s-1) string, 
124            take (e-s) (drop s string),
125            drop e string )  
126
127 unpack :: String -> Ptr CRegMatch -> IO (String)
128 unpack string p_match = do
129   start <- (#peek regmatch_t, rm_so) p_match :: IO CInt
130   end   <- (#peek regmatch_t, rm_eo) p_match :: IO CInt
131   -- the subexpression may not have matched at all, perhaps because it
132   -- was optional.  In this case, the offsets are set to -1.
133   if (start == -1) then return "" else do
134   return (take (fromIntegral (end-start)) (drop (fromIntegral start) string))
135
136 -- -----------------------------------------------------------------------------
137 -- The POSIX regex C interface
138
139 -- Flags for regexec
140 #enum Int,, \
141         REG_NOTBOL, \
142         REG_NOTEOL
143
144 -- Return values from regexec
145 #enum Int,, \
146         REG_NOMATCH
147 --      REG_ESPACE
148
149 -- Flags for regcomp
150 #enum Int,, \
151         REG_EXTENDED, \
152         regIgnoreCase = REG_ICASE, \
153         REG_NOSUB, \
154         REG_NEWLINE
155
156 -- Error codes from regcomp
157 #enum Int,, \
158         REG_BADBR, \
159         REG_BADPAT, \
160         REG_BADRPT, \
161         REG_ECOLLATE, \
162         REG_ECTYPE, \
163         REG_EESCAPE, \
164         REG_ESUBREG, \
165         REG_EBRACK, \
166         REG_EPAREN, \
167         REG_EBRACE, \
168         REG_ERANGE, \
169         REG_ESPACE
170
171 type CRegex    = ()
172 type CRegMatch = ()
173
174 foreign import ccall unsafe "regcomp"
175   c_regcomp :: Ptr CRegex -> CString -> CInt -> IO CInt
176
177 #ifdef __HUGS__
178 foreign import ccall  unsafe "&regfree"
179   ptr_regfree :: FunPtr (Ptr CRegex -> IO ())
180 #else
181 foreign import ccall  unsafe "regfree"
182   c_regfree :: Ptr CRegex -> IO ()
183 #endif /* __HUGS__ */
184
185 foreign import ccall unsafe "regexec"
186   c_regexec :: Ptr CRegex -> CString -> CSize
187             -> Ptr CRegMatch -> CInt -> IO CInt