regex-pcre2 (empty) → 1.0.0.0
raw patch · 11 files changed
+1280/−0 lines, 11 filesdep +HUnitdep +arraydep +basesetup-changed
Dependencies added: HUnit, array, base, bytestring, containers, fail, regex-base, regex-pcre2, utf8-string
Files
- ChangeLog.md +33/−0
- LICENSE +10/−0
- Setup.hs +2/−0
- regex-pcre2.cabal +97/−0
- src/Text/Regex/PCRE2.hs +56/−0
- src/Text/Regex/PCRE2/ByteString.hs +159/−0
- src/Text/Regex/PCRE2/ByteString/Lazy.hs +153/−0
- src/Text/Regex/PCRE2/Sequence.hs +163/−0
- src/Text/Regex/PCRE2/String.hs +146/−0
- src/Text/Regex/PCRE2/Wrap.hsc +419/−0
- test/Main.hs +42/−0
+ ChangeLog.md view
@@ -0,0 +1,33 @@+See also http://pvp.haskell.org/faq++## 1.0.0.0++- Migrate from the obsolete pcre3 library to the new (confusingly-named) pcre2 one++## 0.95.0.0 revision 6++- Allow `containers-0.7`++## 0.95.0.0 revision 5++- Allow `bytestring-0.12`++## 0.95.0.0 revision 4++- Compatibility with `base >= 4.17` (GHC 9.4)++## 0.95.0.0 revision 3++- Compatibility with `base-4.16` (GHC 9.2)++## 0.95.0.0 revision 2++- Compatibility with `base-4.15` (GHC 9.0) and `bytestring-0.11`++## 0.95.0.0++- Update to `regex-0.94.0.0` API+- Compatibility with `base-4.13.0`+- Use `pkg-config` for locating `pcre`++----
+ LICENSE view
@@ -0,0 +1,10 @@+Copyright (c) 2025, Homebrew Holdings+Copyright (c) 2007, Christopher Kuklewicz++Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.+ * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ regex-pcre2.cabal view
@@ -0,0 +1,97 @@+Cabal-Version: 1.12+Name: regex-pcre2+Version: 1.0.0.0++build-type: Simple+license: BSD3+license-file: LICENSE+copyright:+ Copyright (c) 2025, Homebrew Holdings+ Copyright (c) 2006, Christopher Kuklewicz+author: Pete Ryland, based on regex-pcre by Christopher Kuklewicz+maintainer: Pete Ryland <pdr@piglet.ch>+homepage: https://dev.piglet.ch/pdr/regex-pcre2+bug-reports: https://dev.piglet.ch/pdr/regex-pcre2/-/issues+category: Text++synopsis: PCRE2 Backend for "Text.Regex" (regex-base)+description:+ This package provides a <http://pcre.org/ PCRE2> backend for the <//hackage.haskell.org/package/regex-base regex-base> API.++extra-source-files:+ ChangeLog.md++tested-with:+ GHC == 9.6.6++flag pkg-config+ default: True+ manual: True+ description: Use @pkg-config(1)@ to locate foreign @pcre2@ library.++source-repository head+ type: git+ location: https://dev.piglet.ch/pdr/regex-pcre2++source-repository this+ type: git+ location: https://dev.piglet.ch/pdr/regex-pcre2+ tag: v1.0.0.0++library+ hs-source-dirs: src+ exposed-modules:+ Text.Regex.PCRE2+ Text.Regex.PCRE2.Wrap+ Text.Regex.PCRE2.String+ Text.Regex.PCRE2.Sequence+ Text.Regex.PCRE2.ByteString+ Text.Regex.PCRE2.ByteString.Lazy++ other-modules:+ Paths_regex_pcre2++ default-language: Haskell2010+ default-extensions:+ MultiParamTypeClasses+ FunctionalDependencies+ ForeignFunctionInterface+ ScopedTypeVariables+ GeneralizedNewtypeDeriving+ FlexibleContexts+ TypeSynonymInstances+ FlexibleInstances++ build-depends: regex-base == 0.94.*+ , base >= 4.3 && < 5+ , containers >= 0.4 && < 0.8+ , bytestring >= 0.9 && < 0.13+ , array >= 0.3 && < 0.6++ if !impl(ghc >= 8)+ build-depends: fail == 4.9.*++ if flag(pkg-config)+ pkgconfig-depends: libpcre2-8+ else+ extra-libraries: pcre2-8++ ghc-options:+ -O2 -Wall++test-suite regex-pcre2-test+ default-language: Haskell2010+ ghc-options: -Wall+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends: base >= 4.3 && < 5+ , bytestring >= 0.9 && < 0.13+ , HUnit >= 1.6 && < 1.7+ , regex-pcre2+ , utf8-string >= 1.0 && < 1.1++ if flag(pkg-config)+ pkgconfig-depends: libpcre2-8+ else+ extra-libraries: pcre2-8
+ src/Text/Regex/PCRE2.hs view
@@ -0,0 +1,56 @@+{-|+The "Text.Regex.PCRE2" module provides a backend for regular+expressions. If you import this along with other backends, then+you should do so with qualified imports, perhaps renamed for+convenience.++This library uses the newer libpcre2, which supports+UTF8-encoded strings by default.++The regular expression can be provided as a 'ByteString'. The+regular expression and search string are passed as 'CStringLen's+and may contain NUL bytes and do not need to end in a NUL byte.+'ByteString's are searched in place (via unsafeUseAsCStringLen).++A 'String' will be converted into a 'CStringLen' for processing.+Doing this repeatedly will be very inefficient.++The "Text.Regex.PCRE2.String", "Text.Regex.PCRE2.ByteString", and+"Text.Regex.PCRE2.Wrap" modules provide both the high-level interface+exported by this module and medium- and low-level interfaces that+return errors using 'Either' structures.+-}+{- Copyright: (c) 2025 Homebrew Holdings -}+{- Copyright: (c) 2007 Chris Kuklewicz -}+module Text.Regex.PCRE2(getVersion_Text_Regex_PCRE2+ ,module Text.Regex.Base+ -- ** Wrap, for '=~' and '=~~', types and constants+ ,module Text.Regex.PCRE2.Wrap) where++import Prelude hiding (fail)++import Text.Regex.PCRE2.Wrap(+ Regex, CompOption(CompOption), MatchOption(MatchOption),+ (=~), (=~~),+ unusedOffset, getNumSubs, getVersion,+ compBlank, compAnchored, compEndAnchored, compAllowEmptyClass,+ compAltBSUX, compAltExtendedClass, compAltVerbnames,+ compAutoCallout, compCaseless, compDollarEndOnly, compDotAll,+ compDupNames, compExtended, compExtendedMore, compFirstLine,+ compLiteral, compMatchUnsetBackref, compMultiline,+ compNeverBackslashC, compNoAutoCapture, compNoAutoPossess,+ compNoDotstarAnchor, compNoUTFCheck, compUngreedy, compUTF,+ matchBlank, matchAnchored, matchCopyMatchedSubject,+ matchDisableRecurseLoopCheck, matchEndAnchored, matchNotBOL,+ matchNotEOL, matchNotEmpty, matchNotEmptyAtStart,+ matchNoUTFCheck, matchPartialHard, matchPartialSoft)+import Text.Regex.PCRE2.String()+import Text.Regex.PCRE2.Sequence()+import Text.Regex.PCRE2.ByteString()+import Text.Regex.PCRE2.ByteString.Lazy()+import Data.Version(Version(..))+import Text.Regex.Base+import qualified Paths_regex_pcre2++getVersion_Text_Regex_PCRE2 :: Version+getVersion_Text_Regex_PCRE2 = Paths_regex_pcre2.version
+ src/Text/Regex/PCRE2/ByteString.hs view
@@ -0,0 +1,159 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+This exports instances of the high-level API and the medium-level+API of 'compile', 'execute', and 'regexec'.+-}+{- Copyright: (c) 2025 Homebrew Holdings -}+{- Copyright: (c) 2007 Chris Kuklewicz -}+module Text.Regex.PCRE2.ByteString(+ -- ** Types+ Regex,+ MatchOffset,+ MatchLength,+ CompOption(CompOption),+ MatchOption(MatchOption),+ ReturnCode,+ WrapError,+ -- ** Miscellaneous+ unusedOffset,+ getVersion,+ -- ** Medium level API functions+ compile,+ execute,+ regexec,+ -- ** CompOption flags+ compBlank,+ compAnchored,+ compEndAnchored, -- new in v1.0.0.0 (pcre2)+ compAllowEmptyClass, -- new in v1.0.0.0 (pcre2)+ compAltBSUX, -- new in v1.0.0.0 (pcre2)+ compAltExtendedClass, -- new in v1.0.0.0 (pcre2)+ compAltVerbnames, -- new in v1.0.0.0 (pcre2)+ compAutoCallout,+ compCaseless,+ compDollarEndOnly,+ compDotAll,+ compDupNames, -- new in v1.0.0.0 (pcre2)+ compExtended,+ compExtendedMore, -- new in v1.0.0.0 (pcre2)+-- compExtra, -- obsoleted in v1.0.0.0, pcre2 is always strict in this way+ compFirstLine,+ compLiteral, -- new in v1.0.0.0 (pcre2)+ compMatchUnsetBackref, -- new in v1.0.0.0 (pcre2)+ compMultiline,+ compNeverBackslashC, -- new in v1.0.0.0 (pcre2)+ compNoAutoCapture,+ compNoAutoPossess, -- new in v1.0.0.0 (pcre2)+ compNoDotstarAnchor, -- new in v1.0.0.0 (pcre2)+-- compNoUTF8Check, -- obsoleted in v1.0.0.0 (pcre2), use compNoUTFCheck+ compNoUTFCheck,+ compUngreedy,+-- compUTF8, -- obsoleted in v1.0.0.0 (pcre2), use compUTF+ compUTF,+ -- ** MatchOption flags, new to v1.0.0.0 (pcre2), replacing the obsolete ExecOptions+ matchBlank,+ matchAnchored,+ matchCopyMatchedSubject, -- new in v1.0.0.0 (pcre2)+ matchDisableRecurseLoopCheck, -- new in v1.0.0.0 (pcre2)+ matchEndAnchored, -- new in v1.0.0.0 (pcre2)+ matchNotBOL,+ matchNotEOL,+ matchNotEmpty,+ matchNotEmptyAtStart, -- new in v1.0.0.0 (pcre2)+ matchNoUTFCheck,+ matchPartialHard,+ matchPartialSoft+ ) where++import Prelude hiding (fail)+import Control.Monad.Fail (MonadFail(fail))++import Text.Regex.PCRE2.Wrap -- all+import Data.Array(Array,listArray)+import Data.ByteString(ByteString)+import qualified Data.ByteString as B(empty,take,drop,pack)+import qualified Data.ByteString.Unsafe as B(unsafeUseAsCStringLen)+import System.IO.Unsafe(unsafePerformIO)+import Text.Regex.Base.RegexLike(RegexContext(..),RegexMaker(..),RegexLike(..),MatchOffset,MatchLength)+import Text.Regex.Base.Impl(polymatch,polymatchM)+import Foreign.C.String(CStringLen)+import Foreign(nullPtr)++instance RegexContext Regex ByteString ByteString where+ match = polymatch+ matchM = polymatchM++unwrap :: (Show e) => Either e v -> IO v+unwrap x = case x of Left err -> fail ("Text.Regex.PCRE2.ByteString died: "++ show err)+ Right v -> return v++{-# INLINE asCStringLen #-}+asCStringLen :: ByteString -> (CStringLen -> IO a) -> IO a+asCStringLen s op = B.unsafeUseAsCStringLen s checked+ where checked cs@(ptr,_) | ptr == nullPtr = B.unsafeUseAsCStringLen myEmpty (op . trim)+ | otherwise = op cs+ myEmpty = B.pack [0]+ trim (ptr,_) = (ptr,0)++instance RegexMaker Regex CompOption MatchOption ByteString where+ makeRegexOpts c e pattern = unsafePerformIO $+ compile c e pattern >>= unwrap+ makeRegexOptsM c e pattern = either (fail.show) return $ unsafePerformIO $+ compile c e pattern++instance RegexLike Regex ByteString where+ matchTest regex bs = unsafePerformIO $+ asCStringLen bs (wrapTest 0 regex) >>= unwrap+ matchOnce regex bs = unsafePerformIO $+ execute regex bs >>= unwrap+ matchAll regex bs = unsafePerformIO $+ asCStringLen bs (wrapMatchAll regex) >>= unwrap+ matchCount regex bs = unsafePerformIO $+ asCStringLen bs (wrapCount regex) >>= unwrap++-- ---------------------------------------------------------------------+-- | Compiles a regular expression+--+compile :: CompOption -- ^ (summed together)+ -> MatchOption -- ^ (summed together)+ -> ByteString -- ^ The regular expression to compile+ -> IO (Either (MatchOffset,String) Regex) -- ^ Returns: the compiled regular expression+compile c e pattern = B.unsafeUseAsCStringLen pattern (wrapCompile c e)++-- ---------------------------------------------------------------------+-- | Matches a regular expression against a buffer, returning the buffer+-- indicies of the match, and any submatches+--+-- | Matches a regular expression against a string+execute :: Regex -- ^ Compiled regular expression+ -> ByteString -- ^ String to match against+ -> IO (Either WrapError (Maybe (Array Int (MatchOffset,MatchLength))))+ -- ^ Returns: 'Nothing' if the regex did not match the+ -- string, or:+ -- 'Just' an array of (offset,length) pairs where index 0 is whole match, and the rest are the captured subexpressions.+execute regex bs = do+ maybeStartEnd <- asCStringLen bs (wrapMatch 0 regex)+ case maybeStartEnd of+ Right Nothing -> return (Right Nothing)+ Right (Just parts) ->+ return . Right . Just . listArray (0,pred (length parts))+ . map (\(s,e)->(fromIntegral s, fromIntegral (e-s))) $ parts+ Left err -> return (Left err)++regexec :: Regex -- ^ Compiled regular expression+ -> ByteString -- ^ String to match against+ -> IO (Either WrapError (Maybe (ByteString, ByteString, ByteString, [ByteString])))+regexec regex bs = do+ let getSub (start,stop) | start == unusedOffset = B.empty+ | otherwise = B.take (stop-start) . B.drop start $ bs+ matchedParts [] = (B.empty,B.empty,bs,[]) -- no information+ matchedParts (matchedStartStop@(start,stop):subStartStop) =+ (B.take start bs+ ,getSub matchedStartStop+ ,B.drop stop bs+ ,map getSub subStartStop)+ maybeStartEnd <- asCStringLen bs (wrapMatch 0 regex)+ case maybeStartEnd of+ Right Nothing -> return (Right Nothing)+ Right (Just parts) -> return . Right . Just . matchedParts $ parts+ Left err -> return (Left err)
+ src/Text/Regex/PCRE2/ByteString/Lazy.hs view
@@ -0,0 +1,153 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+This exports instances of the high-level API and the medium-level+API of 'compile', 'execute', and 'regexec'.+-}+{- Copyright: (c) 2025 Homebrew Holdings -}+{- Copyright: (c) 2007 Chris Kuklewicz -}+module Text.Regex.PCRE2.ByteString.Lazy(+ -- ** Types+ Regex,+ MatchOffset,+ MatchLength,+ CompOption(CompOption),+ MatchOption(MatchOption),+ ReturnCode,+ WrapError,+ -- ** Miscellaneous+ unusedOffset,+ getVersion,+ -- ** Medium level API functions+ compile,+ execute,+ regexec,+ -- ** CompOption flags+ compBlank,+ compAnchored,+ compEndAnchored, -- new in v1.0.0.0 (pcre2)+ compAllowEmptyClass, -- new in v1.0.0.0 (pcre2)+ compAltBSUX, -- new in v1.0.0.0 (pcre2)+ compAltExtendedClass, -- new in v1.0.0.0 (pcre2)+ compAltVerbnames, -- new in v1.0.0.0 (pcre2)+ compAutoCallout,+ compCaseless,+ compDollarEndOnly,+ compDotAll,+ compDupNames, -- new in v1.0.0.0 (pcre2)+ compExtended,+ compExtendedMore, -- new in v1.0.0.0 (pcre2)+-- compExtra, -- obsoleted in v1.0.0.0, pcre2 is always strict in this way+ compFirstLine,+ compLiteral, -- new in v1.0.0.0 (pcre2)+ compMatchUnsetBackref, -- new in v1.0.0.0 (pcre2)+ compMultiline,+ compNeverBackslashC, -- new in v1.0.0.0 (pcre2)+ compNoAutoCapture,+ compNoAutoPossess, -- new in v1.0.0.0 (pcre2)+ compNoDotstarAnchor, -- new in v1.0.0.0 (pcre2)+-- compNoUTF8Check, -- obsoleted in v1.0.0.0 (pcre2), use compNoUTFCheck+ compNoUTFCheck,+ compUngreedy,+-- compUTF8, -- obsoleted in v1.0.0.0 (pcre2), use compUTF+ compUTF,+ -- ** MatchOption flags, new to v1.0.0.0 (pcre2), replacing the obsolete ExecOptions+ matchBlank,+ matchAnchored,+ matchCopyMatchedSubject, -- new in v1.0.0.0 (pcre2)+ matchDisableRecurseLoopCheck, -- new in v1.0.0.0 (pcre2)+ matchEndAnchored, -- new in v1.0.0.0 (pcre2)+ matchNotBOL,+ matchNotEOL,+ matchNotEmpty,+ matchNotEmptyAtStart, -- new in v1.0.0.0 (pcre2)+ matchNoUTFCheck,+ matchPartialHard,+ matchPartialSoft+ ) where++import Prelude hiding (fail)+import Control.Monad.Fail (MonadFail(fail))++import Text.Regex.PCRE2.Wrap -- all+import Data.Array(Array)+import qualified Data.ByteString.Lazy as L(ByteString,toChunks,fromChunks)+import qualified Data.ByteString as B(ByteString,concat,pack)+import qualified Data.ByteString.Unsafe as B(unsafeUseAsCStringLen)+import System.IO.Unsafe(unsafePerformIO)+import Text.Regex.Base.RegexLike(RegexContext(..),RegexMaker(..),RegexLike(..),MatchOffset,MatchLength)+import Text.Regex.Base.Impl(polymatch,polymatchM)+import qualified Text.Regex.PCRE2.ByteString as BS(execute,regexec)+import Foreign.C.String(CStringLen)+import Foreign(nullPtr)++instance RegexContext Regex L.ByteString L.ByteString where+ match = polymatch+ matchM = polymatchM++{-# INLINE fromLazy #-}+fromLazy :: L.ByteString -> B.ByteString+fromLazy = B.concat . L.toChunks++{-# INLINE toLazy #-}+toLazy :: B.ByteString -> L.ByteString+toLazy = L.fromChunks . return++unwrap :: (Show e) => Either e v -> IO v+unwrap x = case x of Left err -> fail ("Text.Regex.PCRE2.ByteString.Lazy died: "++ show err)+ Right v -> return v++{-# INLINE asCStringLen #-}+asCStringLen :: L.ByteString -> (CStringLen -> IO a) -> IO a+asCStringLen ls op = B.unsafeUseAsCStringLen (fromLazy ls) checked+ where checked cs@(ptr,_) | ptr == nullPtr = B.unsafeUseAsCStringLen myEmpty (op . trim)+ | otherwise = op cs+ myEmpty = B.pack [0]+ trim (ptr,_) = (ptr,0)++instance RegexMaker Regex CompOption MatchOption L.ByteString where+ makeRegexOpts c e pattern = unsafePerformIO $+ compile c e pattern >>= unwrap+ makeRegexOptsM c e pattern = either (fail.show) return $ unsafePerformIO $+ compile c e pattern++instance RegexLike Regex L.ByteString where+ matchTest regex bs = unsafePerformIO $+ asCStringLen bs (wrapTest 0 regex) >>= unwrap+ matchOnce regex bs = unsafePerformIO $+ execute regex bs >>= unwrap+ matchAll regex bs = unsafePerformIO $+ asCStringLen bs (wrapMatchAll regex) >>= unwrap+ matchCount regex bs = unsafePerformIO $+ asCStringLen bs (wrapCount regex) >>= unwrap++-- ---------------------------------------------------------------------+-- | Compiles a regular expression+--+compile :: CompOption -- ^ (summed together)+ -> MatchOption -- ^ (summed together)+ -> L.ByteString -- ^ The regular expression to compile+ -> IO (Either (MatchOffset,String) Regex) -- ^ Returns: the compiled regular expression+compile c e pattern = B.unsafeUseAsCStringLen (fromLazy pattern) (wrapCompile c e)++-- ---------------------------------------------------------------------+-- | Matches a regular expression against a buffer, returning the buffer+-- indicies of the match, and any submatches+--+-- | Matches a regular expression against a string+execute :: Regex -- ^ Compiled regular expression+ -> L.ByteString -- ^ String to match against+ -> IO (Either WrapError (Maybe (Array Int (MatchOffset,MatchLength))))+ -- ^ Returns: 'Nothing' if the regex did not match the+ -- string, or:+ -- 'Just' an array of (offset,length) pairs where index 0 is whole match, and the rest are the captured subexpressions.+execute regex bs = BS.execute regex (fromLazy bs)++regexec :: Regex -- ^ Compiled regular expression+ -> L.ByteString -- ^ String to match against+ -> IO (Either WrapError (Maybe (L.ByteString, L.ByteString, L.ByteString, [L.ByteString])))+regexec regex bs = do+ x <- BS.regexec regex (fromLazy bs)+ return $ case x of+ Left e -> Left e+ Right Nothing -> Right Nothing+ Right (Just (a,b,c,ds)) -> Right (Just (toLazy a,toLazy b,toLazy c,map toLazy ds))
+ src/Text/Regex/PCRE2/Sequence.hs view
@@ -0,0 +1,163 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+This exports instances of the high-level API and the medium-level+API of 'compile', 'execute', and 'regexec'.+-}+{- Copyright: (c) 2025 Homebrew Holdings -}+{- Copyright: (c) 2007 Chris Kuklewicz -}+module Text.Regex.PCRE2.Sequence(+ -- ** Types+ Regex,+ MatchOffset,+ MatchLength,+ CompOption(CompOption),+ MatchOption(MatchOption),+ ReturnCode,+ WrapError,+ -- ** Miscellaneous+ unusedOffset,+ getVersion,+ -- ** Medium level API functions+ compile,+ execute,+ regexec,+ -- ** CompOption flags+ compBlank,+ compAnchored,+ compEndAnchored, -- new in v1.0.0.0 (pcre2)+ compAllowEmptyClass, -- new in v1.0.0.0 (pcre2)+ compAltBSUX, -- new in v1.0.0.0 (pcre2)+ compAltExtendedClass, -- new in v1.0.0.0 (pcre2)+ compAltVerbnames, -- new in v1.0.0.0 (pcre2)+ compAutoCallout,+ compCaseless,+ compDollarEndOnly,+ compDotAll,+ compDupNames, -- new in v1.0.0.0 (pcre2)+ compExtended,+ compExtendedMore, -- new in v1.0.0.0 (pcre2)+-- compExtra, -- obsoleted in v1.0.0.0, pcre2 is always strict in this way+ compFirstLine,+ compLiteral, -- new in v1.0.0.0 (pcre2)+ compMatchUnsetBackref, -- new in v1.0.0.0 (pcre2)+ compMultiline,+ compNeverBackslashC, -- new in v1.0.0.0 (pcre2)+ compNoAutoCapture,+ compNoAutoPossess, -- new in v1.0.0.0 (pcre2)+ compNoDotstarAnchor, -- new in v1.0.0.0 (pcre2)+-- compNoUTF8Check, -- obsoleted in v1.0.0.0 (pcre2), use compNoUTFCheck+ compNoUTFCheck,+ compUngreedy,+-- compUTF8, -- obsoleted in v1.0.0.0 (pcre2), use compUTF+ compUTF,+ -- ** MatchOption flags, new to v1.0.0.0 (pcre2), replacing the obsolete ExecOptions+ matchBlank,+ matchAnchored,+ matchCopyMatchedSubject, -- new in v1.0.0.0 (pcre2)+ matchDisableRecurseLoopCheck, -- new in v1.0.0.0 (pcre2)+ matchEndAnchored, -- new in v1.0.0.0 (pcre2)+ matchNotBOL,+ matchNotEOL,+ matchNotEmpty,+ matchNotEmptyAtStart, -- new in v1.0.0.0 (pcre2)+ matchNoUTFCheck,+ matchPartialHard,+ matchPartialSoft+ ) where++import Prelude hiding (fail)+import Control.Monad.Fail (MonadFail(fail))++import Text.Regex.PCRE2.Wrap -- all+--import Foreign.C.String(withCStringLen,withCString)+import Data.Array(Array,listArray)+import System.IO.Unsafe(unsafePerformIO)+import Text.Regex.Base.RegexLike(RegexMaker(..),RegexLike(..),RegexContext(..),MatchLength,MatchOffset,Extract(..))+import Text.Regex.Base.Impl(polymatch,polymatchM)+import Data.Sequence as S hiding (length)+import qualified Data.Sequence as S (length)+import Foreign.C.String+import Foreign.Marshal.Array+import Foreign.Marshal.Alloc+import Foreign.Storable++instance RegexContext Regex (Seq Char) (Seq Char) where+ match = polymatch+ matchM = polymatchM++unwrap :: (Show e) => Either e v -> IO v+unwrap x = case x of Left err -> fail ("Text.Regex.PCRE2.Sequence died: "++ show err)+ Right v -> return v++instance RegexMaker Regex CompOption MatchOption (Seq Char) where+ makeRegexOpts c e pattern = unsafePerformIO $+ compile c e pattern >>= unwrap+ makeRegexOptsM c e pattern = either (fail.show) return $ unsafePerformIO $+ compile c e pattern++instance RegexLike Regex (Seq Char) where+ matchTest regex str = unsafePerformIO $+ withSeq str (wrapTest 0 regex) >>= unwrap+ matchOnce regex str = unsafePerformIO $+ execute regex str >>= unwrap+ matchAll regex str = unsafePerformIO $+ withSeq str (wrapMatchAll regex) >>= unwrap+ matchCount regex str = unsafePerformIO $+ withSeq str (wrapCount regex) >>= unwrap++-- | Compiles a regular expression+compile :: CompOption -- ^ Flags (summed together)+ -> MatchOption -- ^ Flags (summed together)+ -> (Seq Char) -- ^ The regular expression to compile+ -> IO (Either (MatchOffset,String) Regex) -- ^ Returns: an error string and offset or the compiled regular expression+compile c e pattern = withSeq pattern (wrapCompile c e)++-- | Matches a regular expression against a string+execute :: Regex -- ^ Compiled regular expression+ -> (Seq Char) -- ^ (Seq Char) to match against+ -> IO (Either WrapError (Maybe (Array Int (MatchOffset,MatchLength))))+ -- ^ Returns: 'Nothing' if the regex did not match the+ -- string, or:+ -- 'Just' an array of (offset,length) pairs where index 0 is whole match, and the rest are the captured subexpressions.+execute regex str = do+ maybeStartEnd <- withSeq str (wrapMatch 0 regex)+ case maybeStartEnd of+ Right Nothing -> return (Right Nothing)+-- Right (Just []) -> fail "got [] back!" -- should never happen+ Right (Just parts) ->+ return . Right . Just . listArray (0,pred (length parts))+ . map (\(s,e)->(fromIntegral s, fromIntegral (e-s))) $ parts+ Left err -> return (Left err)++-- | execute match and extract substrings rather than just offsets+regexec :: Regex -- ^ compiled regular expression+ -> (Seq Char) -- ^ string to match+ -> IO (Either WrapError (Maybe ((Seq Char), (Seq Char),(Seq Char), [(Seq Char)])))+ -- ^ Returns: Nothing if no match, else+ -- (text before match, text after match, array of matches with 0 being the whole match)+regexec regex str = do+ let getSub (start,stop) | start == unusedOffset = S.empty+ | otherwise = extract (start,stop-start) str+ matchedParts [] = (S.empty,S.empty,str,[]) -- no information+ matchedParts (matchedStartStop@(start,stop):subStartStop) =+ (before start str+ ,getSub matchedStartStop+ ,after stop str+ ,map getSub subStartStop)+ maybeStartEnd <- withSeq str (wrapMatch 0 regex)+ case maybeStartEnd of+ Right Nothing -> return (Right Nothing)+-- Right (Just []) -> fail "got [] back!" -- should never happen+ Right (Just parts) -> return . Right . Just . matchedParts $ parts+ Left err -> return (Left err)++withSeq :: Seq Char -> (CStringLen -> IO a) -> IO a+withSeq s f =+ let+ len = S.length s+ pokes p a | seq p (seq a False) = undefined+ | otherwise =+ case viewl a of+ EmptyL -> return ()+ c :< a' -> poke p (castCharToCChar c) >> pokes (advancePtr p 1) a'+ in allocaBytes (S.length s) (\ptr -> pokes ptr s >> f (ptr,len))
+ src/Text/Regex/PCRE2/String.hs view
@@ -0,0 +1,146 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+This exports instances of the high-level API and the medium-level+API of 'compile', 'execute', and 'regexec'.+-}+{- Copyright: (c) 2025 Homebrew Holdings -}+{- Copyright: (c) 2007 Chris Kuklewicz -}+module Text.Regex.PCRE2.String(+ -- ** Types+ Regex,+ MatchOffset,+ MatchLength,+ CompOption(CompOption),+ MatchOption(MatchOption),+ ReturnCode,+ WrapError,+ -- ** Miscellaneous+ unusedOffset,+ getVersion,+ -- ** Medium level API functions+ compile,+ execute,+ regexec,+ -- ** CompOption flags+ compBlank,+ compAnchored,+ compEndAnchored, -- new in v1.0.0.0 (pcre2)+ compAllowEmptyClass, -- new in v1.0.0.0 (pcre2)+ compAltBSUX, -- new in v1.0.0.0 (pcre2)+ compAltExtendedClass, -- new in v1.0.0.0 (pcre2)+ compAltVerbnames, -- new in v1.0.0.0 (pcre2)+ compAutoCallout,+ compCaseless,+ compDollarEndOnly,+ compDotAll,+ compDupNames, -- new in v1.0.0.0 (pcre2)+ compExtended,+ compExtendedMore, -- new in v1.0.0.0 (pcre2)+-- compExtra, -- obsoleted in v1.0.0.0, pcre2 is always strict in this way+ compFirstLine,+ compLiteral, -- new in v1.0.0.0 (pcre2)+ compMatchUnsetBackref, -- new in v1.0.0.0 (pcre2)+ compMultiline,+ compNeverBackslashC, -- new in v1.0.0.0 (pcre2)+ compNoAutoCapture,+ compNoAutoPossess, -- new in v1.0.0.0 (pcre2)+ compNoDotstarAnchor, -- new in v1.0.0.0 (pcre2)+-- compNoUTF8Check, -- obsoleted in v1.0.0.0 (pcre2), use compNoUTFCheck+ compNoUTFCheck,+ compUngreedy,+-- compUTF8, -- obsoleted in v1.0.0.0 (pcre2), use compUTF+ compUTF,+ -- ** MatchOption flags, new to v1.0.0.0 (pcre2), replacing the obsolete ExecOptions+ matchBlank,+ matchAnchored,+ matchCopyMatchedSubject, -- new in v1.0.0.0 (pcre2)+ matchDisableRecurseLoopCheck, -- new in v1.0.0.0 (pcre2)+ matchEndAnchored, -- new in v1.0.0.0 (pcre2)+ matchNotBOL,+ matchNotEOL,+ matchNotEmpty,+ matchNotEmptyAtStart, -- new in v1.0.0.0 (pcre2)+ matchNoUTFCheck,+ matchPartialHard,+ matchPartialSoft+ ) where++import Prelude hiding (fail)+import Control.Monad.Fail (MonadFail(fail))++import Text.Regex.PCRE2.Wrap -- all+import Foreign.C.String(withCStringLen)+import Data.Array(Array,listArray)+import System.IO.Unsafe(unsafePerformIO)+import Text.Regex.Base.RegexLike(RegexMaker(..),RegexLike(..),RegexContext(..),MatchLength,MatchOffset)+import Text.Regex.Base.Impl(polymatch,polymatchM)++instance RegexContext Regex String String where+ match = polymatch+ matchM = polymatchM++unwrap :: (Show e) => Either e v -> IO v+unwrap x = case x of Left err -> fail ("Text.Regex.PCRE2.String died: "++ show err)+ Right v -> return v++instance RegexMaker Regex CompOption MatchOption String where+ makeRegexOpts c e pattern = unsafePerformIO $+ compile c e pattern >>= unwrap+ makeRegexOptsM c e pattern = either (fail.show) return $ unsafePerformIO $+ compile c e pattern++instance RegexLike Regex String where+ matchTest regex str = unsafePerformIO $+ withCStringLen str (wrapTest 0 regex) >>= unwrap+ matchOnce regex str = unsafePerformIO $+ execute regex str >>= unwrap+ matchAll regex str = unsafePerformIO $ + withCStringLen str (wrapMatchAll regex) >>= unwrap+ matchCount regex str = unsafePerformIO $ + withCStringLen str (wrapCount regex) >>= unwrap++-- | Compiles a regular expression+compile :: CompOption -- ^ Flags (summed together)+ -> MatchOption -- ^ Flags (summed together)+ -> String -- ^ The regular expression to compile+ -> IO (Either (MatchOffset,String) Regex) -- ^ Returns: an error string and offset or the compiled regular expression+compile c e pattern = withCStringLen pattern (wrapCompile c e)++-- | Matches a regular expression against a string+execute :: Regex -- ^ Compiled regular expression+ -> String -- ^ String to match against+ -> IO (Either WrapError (Maybe (Array Int (MatchOffset,MatchLength))))+ -- ^ Returns: 'Nothing' if the regex did not match the+ -- string, or:+ -- 'Just' an array of (offset,length) pairs where index 0 is whole match, and the rest are the captured subexpressions.+execute regex str = do+ maybeStartEnd <- withCStringLen str (wrapMatch 0 regex)+ case maybeStartEnd of+ Right Nothing -> return (Right Nothing)+-- Right (Just []) -> fail "got [] back!" -- should never happen+ Right (Just parts) -> + return . Right . Just . listArray (0,pred (length parts))+ . map (\(s,e)->(fromIntegral s, fromIntegral (e-s))) $ parts+ Left err -> return (Left err)++-- | execute match and extract substrings rather than just offsets+regexec :: Regex -- ^ compiled regular expression+ -> String -- ^ string to match+ -> IO (Either WrapError (Maybe (String, String,String, [String])))+ -- ^ Returns: Nothing if no match, else+ -- (text before match, text after match, array of matches with 0 being the whole match)+regexec regex str = do+ let getSub (start,stop) | start == unusedOffset = ""+ | otherwise = take (stop-start) . drop start $ str+ matchedParts [] = ("","",str,[]) -- no information+ matchedParts (matchedStartStop@(start,stop):subStartStop) = + (take start str+ ,getSub matchedStartStop+ ,drop stop str+ ,map getSub subStartStop)+ maybeStartEnd <- withCStringLen str (wrapMatch 0 regex)+ case maybeStartEnd of+ Right Nothing -> return (Right Nothing)+-- Right (Just []) -> fail "got [] back!" -- should never happen+ Right (Just parts) -> return . Right . Just . matchedParts $ parts+ Left err -> return (Left err)
+ src/Text/Regex/PCRE2/Wrap.hsc view
@@ -0,0 +1,419 @@+-- The exported symbols are the same whether HAVE_PCRE2_H is defined,+-- but when if it is not defined then 'getVersion == Nothing' and all+-- other exported values will call error or fail.++-- | This will fail or error only if allocation fails or a nullPtr is passed in.++-- TODO :: Consider wrapMatchAll using list of start/end offsets and not MatchArray+--++{- Copyright : (c) Chris Kuklewicz 2007 -}+module Text.Regex.PCRE2.Wrap(+ -- ** High-level interface+ Regex,+ CompOption(CompOption),+ MatchOption(MatchOption),+ (=~),+ (=~~),++ -- ** Low-level interface+ StartOffset,+ EndOffset,+ ReturnCode(ReturnCode),+ WrapError,+ wrapCompile,+ wrapTest,+ wrapMatch,+ wrapMatchAll,+ wrapCount,++ -- ** Miscellaneous+ getVersion,+ getNumSubs,+ unusedOffset,++ -- ** CompOption values+ compBlank,+ compAnchored,+ compEndAnchored, -- new in v1.0.0.0 (pcre2)+ compAllowEmptyClass, -- new in v1.0.0.0 (pcre2)+ compAltBSUX, -- new in v1.0.0.0 (pcre2)+ compAltExtendedClass, -- new in v1.0.0.0 (pcre2)+ compAltVerbnames, -- new in v1.0.0.0 (pcre2)+ compAutoCallout,+ compCaseless,+ compDollarEndOnly,+ compDotAll,+ compDupNames, -- new in v1.0.0.0 (pcre2)+ compExtended,+ compExtendedMore, -- new in v1.0.0.0 (pcre2)+ compFirstLine,+ compLiteral, -- new in v1.0.0.0 (pcre2)+ compMatchUnsetBackref, -- new in v1.0.0.0 (pcre2)+ compMultiline,+ compNeverBackslashC, -- new in v1.0.0.0 (pcre2)+ compNoAutoCapture,+ compNoAutoPossess, -- new in v1.0.0.0 (pcre2)+ compNoDotstarAnchor, -- new in v1.0.0.0 (pcre2)+ compNoUTFCheck,+ compUngreedy,+ compUTF,++ matchBlank,+ matchAnchored,+ matchCopyMatchedSubject, -- new in v1.0.0.0 (pcre2)+ matchDisableRecurseLoopCheck, -- new in v1.0.0.0 (pcre2)+ matchEndAnchored, -- new in v1.0.0.0 (pcre2)+ matchNotBOL,+ matchNotEOL,+ matchNotEmpty,+ matchNotEmptyAtStart, -- new in v1.0.0.0 (pcre2)+ matchNoUTFCheck,+ matchPartialHard,+ matchPartialSoft, -- equivalent to the obsolete execPartial++ -- ** ReturnCode values+ retOk,+ retNoMatch,+ retPartial, -- new in v1.0.0.0 (pcre2)+ retNull,+ retBadOption,+ retBadMagic,+ retNoMemory,+ retNoSubstring+ ) where++import Prelude hiding (fail)+import Control.Monad.Fail (MonadFail(fail))++import Control.Exception(bracket)+import Control.Monad(when)+import Data.Array(Array,accumArray)+import Data.Bits(Bits((.|.)))+import Data.Word(Word32)+import System.IO.Unsafe(unsafePerformIO)+import Foreign(Ptr,ForeignPtr,FinalizerPtr -- ,FunPtr+ ,alloca,allocaBytes,nullPtr+ ,mallocBytes,free+ ,peek,peekElemOff+ ,newForeignPtr,withForeignPtr)+#if __GLASGOW_HASKELL__ >= 703+import Foreign.C(CInt(CInt),CSize(CSize))+#else+import Foreign.C(CInt,CSize)+#endif+import Foreign.C.String(CString,CStringLen,peekCString)+import Text.Regex.Base.RegexLike(RegexOptions(..),RegexMaker(..),RegexContext(..),MatchArray,MatchOffset)++-- | Version string of PCRE2 library+{-# NOINLINE getVersion #-}+getVersion :: Maybe String++type PCRE = ()+type CompContext = ()+type MatchContext = ()+type MatchData = ()+type StartOffset = MatchOffset+type EndOffset = MatchOffset+type WrapError = (ReturnCode,String)++newtype CompOption = CompOption Word32 deriving (Eq,Show,Num,Bits)+newtype MatchOption = MatchOption Word32 deriving (Eq,Show,Num,Bits)+newtype ReturnCode = ReturnCode CInt deriving (Eq,Show)++-- | A compiled regular expression+data Regex = Regex (ForeignPtr PCRE) CompOption MatchOption Int++compBlank :: CompOption+matchBlank :: MatchOption+unusedOffset :: MatchOffset+retOk :: ReturnCode++wrapCompile :: CompOption -- ^ Flags (summed together)+ -> MatchOption -- ^ Flags (summed together)+ -> CStringLen -- ^ The regular expression to compile+ -> IO (Either (MatchOffset,String) Regex) -- ^ Returns: an error offset and string or the compiled regular expression+wrapTest :: StartOffset -- ^ Starting index in CStringLen+ -> Regex -- ^ Compiled regular expression+ -> CStringLen -- ^ String to match against and length in bytes+ -> IO (Either WrapError Bool)+wrapMatch :: StartOffset -- ^ Starting index in CStringLen+ -> Regex -- ^ Compiled regular expression+ -> CStringLen -- ^ String to match against and length in bytes+ -> IO (Either WrapError (Maybe [(StartOffset,EndOffset)]))+ -- ^ Returns: 'Right Nothing' if the regex did not match the string, or:+ -- 'Right Just' an array of (offset,length) pairs where index 0 is whole match, and the rest are the captured subexpressions, or:+ -- 'Left ReturnCode' if there is some strange error+wrapMatchAll :: Regex -> CStringLen -> IO (Either WrapError [ MatchArray ])+wrapCount :: Regex -> CStringLen -> IO (Either WrapError Int)++getNumSubs :: Regex -> Int++(=~) :: (RegexMaker Regex CompOption MatchOption source,RegexContext Regex source1 target)+ => source1 -> source -> target+(=~~) :: (RegexMaker Regex CompOption MatchOption source,RegexContext Regex source1 target,MonadFail m)+ => source1 -> source -> m target++#include <sys/types.h>+#define PCRE2_CODE_UNIT_WIDTH 8+#include <pcre2.h>++instance RegexOptions Regex CompOption MatchOption where+ blankCompOpt = compBlank+ blankExecOpt = matchBlank+ defaultCompOpt = compMultiline+ defaultExecOpt = matchBlank+ setExecOpts e' (Regex r c _ n) = Regex r c e' n+ getExecOpts (Regex _ _ e _) = e++-- (=~) :: (RegexMaker Regex CompOption MatchOption source,RegexContext Regex source1 target) => source1 -> source -> target+(=~) x r = let q :: Regex+ q = makeRegex r+ in match q x++-- (=~~) ::(RegexMaker Regex CompOption MatchOption source,RegexContext Regex source1 target,MonadFail m) => source1 -> source -> m target+(=~~) x r = do (q :: Regex) <- makeRegexM r+ matchM q x++fi :: (Integral i,Num n) => i -> n+fi x = fromIntegral x++compBlank = CompOption 0+matchBlank = MatchOption 0+unusedOffset = (-1)+retOk = ReturnCode 0++newtype InfoWhat = InfoWhat Word32 deriving (Eq,Show)+newtype ConfigWhat = ConfigWhat Word32 deriving (Eq,Show)++nullTest' :: Ptr a -> String -> IO (Either (MatchOffset,String) b) -> IO (Either (MatchOffset,String) b)+{-# INLINE nullTest' #-}+nullTest' ptr msg io = do+ if nullPtr == ptr+ then return (Left (0,"Ptr parameter was nullPtr in Text.Regex.PCRE2.Wrap."++msg))+ else io++nullTest :: Ptr a -> String -> IO (Either WrapError b) -> IO (Either WrapError b)+{-# INLINE nullTest #-}+nullTest ptr msg io = do+ if nullPtr == ptr+ then return (Left (retOk,"Ptr parameter was nullPtr in Text.Regex.PCRE2.Wrap."++msg))+ else io++getErrMsg :: CInt -> IO String+{-# INLINE getErrMsg #-}+getErrMsg errnum = do+ errstr <- mallocBytes 1024+ if nullPtr == errstr+ then return "Ptr parameter was nullPtr in Text.Regex.PCRE2.Wrap.getErrMsg errstr"+ else do+ _ <- c_pcre2_get_error_message errnum errstr 1024+ errstr' <- peekCString errstr+ free errstr+ return errstr'++wrapRC :: ReturnCode -> IO (Either WrapError b)+{-# INLINE wrapRC #-}+wrapRC errnum@(ReturnCode errnum') = do+ errstr <- getErrMsg errnum'+ return (Left (errnum,"Error in Text.Regex.PCRE2.Wrap: "++errstr))++-- | Compiles a regular expression+wrapCompile flags e (pattern,len) = do+ nullTest' pattern "wrapCompile pattern" $ do+ alloca $ \errOffset -> alloca $ \errPtr -> do+ nullTest' errPtr "wrapCompile errPtr" $ do+ pcre_ptr <- c_pcre2_compile pattern (fi len) flags errPtr errOffset nullPtr+ if pcre_ptr == nullPtr+ then do+ -- No need to use c_pcre2_code_free in the error case (e.g. pcredemo.c)+ offset <- peek errOffset+ errstr <- getErrMsg =<< peek errPtr+ return (Left (fi offset, errstr))+ else do+ alloca $ \st -> do+ when (st == nullPtr) (fail "Text.Regex.PCRE2.Wrap.wrapCompile could not allocate a CInt for the capture count.")+ ok0 <- c_pcre2_pattern_info pcre_ptr pcre2InfoCapturecount st+ when (ok0 /= 0) (fail $ "Impossible/fatal: Haskell package regex-pcre2 error in Text.Posix.PCRE2.Wrap.getNumSubs' of ok0 /= 0. ok0 is from pcre2_pattern_info c-function which returned "++show ok0)+ n <- peek st+ regex <- newForeignPtr c_pcre2_code_free pcre_ptr+ return . Right $ Regex regex flags e n++getNumSubs (Regex _ _ _ n) = n++withDataPtr :: IO (Ptr MatchData) -> String -> (Ptr MatchData -> IO (Either WrapError a)) -> IO (Either WrapError a)+withDataPtr data_create jobname job = bracket data_create c_pcre2_match_data_free job'+ where+ job' dataPtr = nullTest dataPtr (jobname++" dataPtr") (job dataPtr)++wrapTest startOffset (Regex pcre_fptr _ flags _) (cstr,len) = do+ nullTest cstr "wrapTest cstr" $ do+ withForeignPtr pcre_fptr $ \pcre_ptr -> do+ withDataPtr (c_pcre2_match_data_create 1 nullPtr) "wrapTest" $ \dataPtr -> do+ r@(ReturnCode r') <- c_pcre2_match pcre_ptr cstr (fi len) (fi startOffset) flags dataPtr nullPtr+ if r == retNoMatch+ then return (Right False)+ else if r' < 0+ then wrapRC r+ else return (Right True)++-- | Matches a regular expression against a string+--+-- Should never return (Right (Just []))+wrapMatch startOffset (Regex pcre_fptr _ flags nsub) (cstr,len) = do+ nullTest cstr "wrapMatch cstr" $ do+ withForeignPtr pcre_fptr $ \pcre_ptr -> do+ withDataPtr (c_pcre2_match_data_create_from_pattern pcre_ptr nullPtr) "wrapMatch" $ \dataPtr -> do+ r@(ReturnCode r') <- c_pcre2_match pcre_ptr cstr (fi len) (fi startOffset) flags dataPtr nullPtr+ if r == retNoMatch+ then do+ return (Right Nothing)+ else if r' < 0+ then wrapRC r+ else do+ ovecsize <- fi <$> c_pcre2_get_ovector_count dataPtr+ ovec <- c_pcre2_get_ovector_pointer dataPtr+ let extraPairs :: [(Int,Int)]+ extraPairs = replicate (nsub + 1 - ovecsize) (unusedOffset,unusedOffset)+ pairs <- return . toPairs =<< mapM (peekElemOff ovec) [0 .. ((ovecsize*2)-1)]+ return . Right . Just $ (pairs ++ extraPairs)++-- | wrapMatchAll is an improvement over wrapMatch since it only+-- allocates memory with allocaBytes once at the start.+wrapMatchAll (Regex pcre_fptr _ flags nsub) (cstr,len) = do+ nullTest cstr "wrapMatchAll cstr" $ do+ withForeignPtr pcre_fptr $ \pcre_ptr -> do+ let flags' = (matchNotEmpty .|. matchAnchored .|. flags)+ withDataPtr (c_pcre2_match_data_create_from_pattern pcre_ptr nullPtr) "wrapMatchAll" $ \dataPtr ->+ let loop acc flags_in_use pos = do+ r@(ReturnCode r') <- c_pcre2_match pcre_ptr cstr (fi len) (fi pos) flags_in_use dataPtr nullPtr+ if r == retNoMatch+ then return (Right (acc []))+ else if r' < 0+ then wrapRC r+ else do+ ovecsize <- fi <$> c_pcre2_get_ovector_count dataPtr+ ovec <- c_pcre2_get_ovector_pointer dataPtr+ pairs <- return . toPairs =<< mapM (peekElemOff ovec) [0 .. ((ovecsize*2)-1)]+ let acc' = acc . (toMatchArray nsub pairs:)+ case pairs of+ [] -> return (Right (acc' []))+ ((s,e):_) | s==e -> if s == len+ then return (Right (acc' []))+ else loop acc' flags' e+ | otherwise -> loop acc' flags e+ in loop id flags 0+toMatchArray :: Int -> [(Int,Int)] -> Array Int (Int,Int)+toMatchArray n pairs = accumArray (\_ (s,e) -> (s,(e-s))) (-1,0) (0,n) (zip [0..] pairs)++toPairs :: [CSize] -> [(Int,Int)]+toPairs [] = []+toPairs (a:b:rest) = (fi a,fi b):toPairs rest+toPairs [_] = error "Should not have just one element in Text.Regex.Wrap.PCRE2.toPairs"++wrapCount (Regex pcre_fptr _ flags _) (cstr,len) = do+ nullTest cstr "wrapCount cstr" $ do+ withForeignPtr pcre_fptr $ \pcre_ptr -> do+ withDataPtr (c_pcre2_match_data_create_from_pattern pcre_ptr nullPtr) "wrapCount" $ \dataPtr ->+ let act pos = c_pcre2_match pcre_ptr cstr (fi len) (fi pos) flags dataPtr nullPtr+ loop acc pos | acc `seq` pos `seq` False = undefined+ | otherwise = do+ r@(ReturnCode r') <- act pos+ if r == retNoMatch+ then return (Right acc)+ else if r' < 0+ then wrapRC r+ else do+ ovec <- c_pcre2_get_ovector_pointer dataPtr+ pairs <- return . toPairs =<< mapM (peekElemOff ovec) [0,1]+ case pairs of+ [] -> return (Right (succ acc))+ ((s,e):_) | s==e -> return (Right (succ acc))+ | otherwise -> loop (succ acc) e+ in loop 0 0++getVersion = unsafePerformIO $ do+ vsize <- c_pcre2_config pcre2ConfigVersion nullPtr+ allocaBytes vsize $ \v -> do+ if v == nullPtr+ then return Nothing+ else do+ _ <- c_pcre2_config pcre2ConfigVersion v+ Just <$> peekCString v++foreign import ccall unsafe "pcre2.h pcre2_config_8"+ c_pcre2_config :: ConfigWhat -> Ptr a -> IO Int+foreign import ccall unsafe "pcre2.h pcre2_compile_8"+ c_pcre2_compile :: CString -> CSize -> CompOption -> Ptr CInt -> Ptr CSize -> Ptr CompContext -> IO (Ptr PCRE)+foreign import ccall unsafe "pcre2.h pcre2_get_error_message_8"+ c_pcre2_get_error_message :: CInt -> CString -> CSize -> IO CInt+foreign import ccall unsafe "pcre2.h pcre2_pattern_info_8"+ c_pcre2_pattern_info :: Ptr PCRE -> InfoWhat -> Ptr a -> IO CInt+foreign import ccall unsafe "pcre2.h &pcre2_code_free_8"+ c_pcre2_code_free :: FinalizerPtr PCRE+foreign import ccall unsafe "pcre2.h pcre2_match_data_create_8"+ c_pcre2_match_data_create :: Word32 -> Ptr MatchContext -> IO (Ptr MatchData)+foreign import ccall unsafe "pcre2.h pcre2_match_data_create_from_pattern_8"+ c_pcre2_match_data_create_from_pattern :: Ptr PCRE -> Ptr MatchContext -> IO (Ptr MatchData)+foreign import ccall unsafe "pcre2.h pcre2_match_8"+ c_pcre2_match :: Ptr PCRE -> CString -> CSize -> CSize -> MatchOption -> Ptr MatchData -> Ptr MatchContext -> IO ReturnCode+foreign import ccall unsafe "pcre2.h pcre2_get_ovector_count_8"+ c_pcre2_get_ovector_count :: Ptr MatchData -> IO Word32+foreign import ccall unsafe "pcre2.h pcre2_get_ovector_pointer_8"+ c_pcre2_get_ovector_pointer :: Ptr MatchData -> IO (Ptr CSize)+foreign import ccall unsafe "pcre2.h pcre2_match_data_free_8"+ c_pcre2_match_data_free :: Ptr MatchData -> IO ()++#enum CompOption,CompOption, \+ compAllowEmptyClass = PCRE2_ALLOW_EMPTY_CLASS, \+ compAltBSUX = PCRE2_ALT_BSUX, \+ compAltExtendedClass = PCRE2_ALT_EXTENDED_CLASS, \+ compAltVerbnames = PCRE2_ALT_VERBNAMES, \+ compAnchored = PCRE2_ANCHORED, \+ compAutoCallout = PCRE2_AUTO_CALLOUT, \+ compCaseless = PCRE2_CASELESS, \+ compDollarEndOnly = PCRE2_DOLLAR_ENDONLY, \+ compDotAll = PCRE2_DOTALL, \+ compDupNames = PCRE2_DUPNAMES, \+ compEndAnchored = PCRE2_ENDANCHORED, \+ compExtended = PCRE2_EXTENDED, \+ compExtendedMore = PCRE2_EXTENDED_MORE, \+ compFirstLine = PCRE2_FIRSTLINE, \+ compLiteral = PCRE2_LITERAL, \+ compMatchUnsetBackref = PCRE2_MATCH_UNSET_BACKREF, \+ compMultiline = PCRE2_MULTILINE, \+ compNeverBackslashC = PCRE2_NEVER_BACKSLASH_C, \+ compNoAutoCapture = PCRE2_NO_AUTO_CAPTURE, \+ compNoAutoPossess = PCRE2_NO_AUTO_POSSESS, \+ compNoDotstarAnchor = PCRE2_NO_DOTSTAR_ANCHOR, \+ compNoUTFCheck = PCRE2_NO_UTF_CHECK, \+ compUngreedy = PCRE2_UNGREEDY, \+ compUTF = PCRE2_UTF++#enum MatchOption,MatchOption, \+ matchAnchored = PCRE2_ANCHORED, \+ matchCopyMatchedSubject = PCRE2_COPY_MATCHED_SUBJECT, \+ matchDisableRecurseLoopCheck = PCRE2_DISABLE_RECURSELOOP_CHECK, \+ matchEndAnchored = PCRE2_ENDANCHORED, \+ matchNotBOL = PCRE2_NOTBOL, \+ matchNotEOL = PCRE2_NOTEOL, \+ matchNotEmpty = PCRE2_NOTEMPTY, \+ matchNotEmptyAtStart = PCRE2_NOTEMPTY_ATSTART, \+ matchNoUTFCheck = PCRE2_NO_UTF_CHECK, \+ matchPartialHard = PCRE2_PARTIAL_HARD, \+ matchPartialSoft = PCRE2_PARTIAL_SOFT++#enum ReturnCode,ReturnCode, \+ retNoMatch = PCRE2_ERROR_NOMATCH, \+ retPartial = PCRE2_ERROR_PARTIAL, \+ retNull = PCRE2_ERROR_NULL, \+ retBadOption = PCRE2_ERROR_BADOPTION, \+ retBadMagic = PCRE2_ERROR_BADMAGIC, \+ retNoMemory = PCRE2_ERROR_NOMEMORY, \+ retNoSubstring = PCRE2_ERROR_NOSUBSTRING++#enum InfoWhat,InfoWhat, \+ PCRE2_INFO_CAPTURECOUNT++#enum ConfigWhat,ConfigWhat, \+ PCRE2_CONFIG_VERSION
+ test/Main.hs view
@@ -0,0 +1,42 @@+{- Copyright: (c) 2025 Homebrew Holdings -}+module Main where++import Text.Regex.PCRE2+import Test.HUnit+import qualified Data.ByteString.UTF8 as BSU+import qualified System.Exit as Exit++test_tester :: Test+test_tester = TestCase $ assertBool "always True" $ (1 :: Int) == 1++test_version :: Test+test_version = TestCase $ assertBool "should be a non-empty string"+ $ maybe False ((>4) . length) getVersion++test_string :: Test+test_string = TestCase $ assertBool "should match successfully"+ $ "abcdef" =~ "[abc]+[def]{3}"++test_bytestring :: Test+test_bytestring = TestCase $ assertBool "should match successfully"+ $ (BSU.fromString "abbabbbbabbabc") =~ (BSU.fromString "^(abba|bbbb|bc)*$")++emailRegex :: String+emailRegex = "<([^>@]*)@([a-zA-Z.-]*)>"++test_capture :: Test+test_capture = TestCase $ assertEqual "should capture correctly"+ ("abc","<test@example.com>","xyz",["test","example.com"])+ $ "abc<test@example.com>xyz" =~ emailRegex++tests :: Test+tests = TestList [ TestLabel "test_tester" test_tester+ , TestLabel "test_version" test_version+ , TestLabel "test_string" test_string+ , TestLabel "test_bytestring" test_bytestring+ , TestLabel "test_capture" test_capture ]++main :: IO ()+main = do+ result <- runTestTT tests+ if failures result > 0 then Exit.exitFailure else Exit.exitSuccess