pcre-light 0.3.1.1 → 0.4.1.3
raw patch · 13 files changed
Files
- ChangeLog.md +15/−0
- README.md +11/−0
- TODO +0/−2
- Text/Regex/PCRE/Light.hs +92/−24
- Text/Regex/PCRE/Light/Base.hsc +13/−1
- Text/Regex/PCRE/Light/Char8.hs +20/−6
- configure +0/−9
- pcre-light.buildinfo.in +0/−3
- pcre-light.cabal +39/−27
- tests/Makefile +0/−11
- tests/Parse.hs +0/−75
- tests/Unit.hs +76/−30
- tests/failure1.hs +0/−5
+ ChangeLog.md view
@@ -0,0 +1,15 @@+# Changelog for pcre-light++## 0.4.1.3+- Remove old-base flag.+- Fix [#18](https://codeberg.org/daniel-casanueva/pcre-light/issues/18).+- Metadata update.++## 0.4.1.2+- Replace finalizerFree with c_pcre_free. ([PR 17](https://gitlab.com/daniel-casanueva/haskell/pcre-light/-/merge_requests/17))++## 0.4.1.1+- Bugfix where ByteString.empty was treated differently than the empty ByteString `""`++## 0.4.1.0+- Add `captureCount` and `captureNames` for working with named captures/groups
+ README.md view
@@ -0,0 +1,11 @@++# pcre-light library++A small, efficient and portable regex library for Perl 5 compatible regular expressions++The PCRE library is a set of functions that implement regular+expression pattern matching using the same syntax and semantics as Perl 5.++## Authorship++This library was originally written by Donald Stewart.
− TODO
@@ -1,2 +0,0 @@-* backwards compatibility with older libpcres (e.g. pcre3 on debian)-* some tests fail for strange reasons
Text/Regex/PCRE/Light.hs view
@@ -10,7 +10,7 @@ -- Portability: H98 + CPP -- ----------------------------------------------------------------------- +-- -- A simple, portable binding to perl-compatible regular expressions -- (PCRE) via strict ByteStrings. --@@ -23,6 +23,8 @@ -- * ByteString interface , compile, compileM , match+ , captureCount+ , captureNames -- * Regex types and constructors externally visible @@ -80,8 +82,12 @@ import qualified Data.ByteString.Base as S #endif +import System.IO.Unsafe (unsafePerformIO)+import Data.List (sortBy)+import Data.Function (on)+ -- Foreigns-import Foreign+import Foreign (newForeignPtr, withForeignPtr) import Foreign.Ptr import Foreign.C.Types import Foreign.C.String@@ -102,7 +108,7 @@ -- > let r = compile "^(b+|a){1,2}?bc" [] -- -- If the regular expression is invalid, an exception is thrown.--- If this is unsuitable, 'compileM' is availlable, which returns failure +-- If this is unsuitable, 'compileM' is availlable, which returns failure -- in a monad. -- -- To do case insentive matching,@@ -116,7 +122,7 @@ -- -- The arguments are: ----- * 'pat': A ByteString containing the regular expression to be compiled. +-- * 'pat': A ByteString containing the regular expression to be compiled. -- -- * 'flags', optional bit flags. If 'Nothing' is provided, defaults are used. --@@ -195,15 +201,15 @@ compileM str os = unsafePerformIO $ S.useAsCString str $ \pattern -> do alloca $ \errptr -> do- alloca $ \erroffset -> do- pcre_ptr <- c_pcre_compile pattern (combineOptions os) errptr erroffset nullPtr- if pcre_ptr == nullPtr- then do- err <- peekCString =<< peek errptr- return (Left err)- else do- reg <- newForeignPtr finalizerFree pcre_ptr -- release with free()- return (Right (Regex reg str))+ alloca $ \erroffset -> do+ pcre_ptr <- c_pcre_compile pattern (combineOptions os) errptr erroffset nullPtr+ if pcre_ptr == nullPtr+ then do+ err <- peekCString =<< peek errptr+ return (Left err)+ else do+ reg <- newForeignPtr c_pcre_free pcre_ptr+ return (Right (Regex reg str)) -- Possible improvements: an 'IsString' instance could be defined -- for 'Regex', which would allow the compiler to insert calls to@@ -264,7 +270,7 @@ match :: Regex -> S.ByteString -> [PCREExecOption] -> Maybe [S.ByteString] match (Regex pcre_fp _) subject os = unsafePerformIO $ do withForeignPtr pcre_fp $ \pcre_ptr -> do- n_capt <- capturedCount pcre_ptr+ n_capt <- fullInfoInt pcre_ptr info_capturecount -- The smallest size for ovector that will allow for n captured -- substrings, in addition to the offsets of the substring@@ -277,16 +283,24 @@ let (str_fp, off, len) = S.toForeignPtr subject withForeignPtr str_fp $ \cstr -> do- r <- c_pcre_exec+ let exec csub clen = c_pcre_exec pcre_ptr nullPtr- (cstr `plusPtr` off) -- may contain binary zero bytes.- (fromIntegral len)+ csub -- may contain binary zero bytes.+ (fromIntegral clen) 0 (combineExecOptions os) ovec (fromIntegral ovec_size) + -- An empty ByteString may be represented with a nullPtr. Passing+ -- a nullPtr to pcre_exec will cause it to return an error, even if+ -- the pattern could succesfully match an empty subject. As a+ -- workaround, allocate a small buffer and pass that to pcre_exec.+ r <- if cstr == nullPtr+ then allocaBytes 1 $ \buf -> exec buf 0+ else exec (cstr `plusPtr` off) len+ if r < 0 -- errors, or error_no_match then return Nothing else let loop n o acc =@@ -308,7 +322,7 @@ -- pair is used for the first capturing subpattern, and so on. The -- value returned by pcre_exec() is one more than the highest num- bered -- pair that has been set. For example, if two sub- strings have been- -- captured, the returned value is 3. + -- captured, the returned value is 3. where -- The first element of a pair is set to the offset of the first@@ -321,9 +335,63 @@ start = S.unsafeDrop (fromIntegral a) s end = S.unsafeTake (fromIntegral (b-a)) start - -- use pcre_info to work out how many substrings to reserve space for- capturedCount :: Ptr PCRE -> IO Int- capturedCount regex_ptr =- alloca $ \n_ptr -> do -- (st :: Ptr CInt)- c_pcre_fullinfo regex_ptr nullPtr info_capturecount n_ptr- return . fromIntegral =<< peek (n_ptr :: Ptr CInt)++-- Wrapper around c_pcre_fullinfo for integer values+fullInfoInt pcre_ptr what =+ alloca $ \n_ptr -> do+ c_pcre_fullinfo pcre_ptr nullPtr what n_ptr+ return . fromIntegral =<< peek (n_ptr :: Ptr CInt)+++-- | 'captureCount'+--+-- Returns the number of captures in a 'Regex'. Correctly ignores non-capturing groups+-- like @(?:abc)@.+--+-- >>> captureCount (compile "(?<one>abc) (def) (?:non-captured) (?<three>ghi)" [])+-- 3+captureCount :: Regex -> Int+captureCount (Regex pcre_fp _) = unsafePerformIO $+ withForeignPtr pcre_fp $ \pcre_ptr ->+ fullInfoInt pcre_ptr info_capturecount+++-- | 'captureNames'+--+-- Returns the names and numbers of all named subpatterns in the regular+-- expression. Groups are zero-indexed. Unnamed groups are counted, but don't appear in the+-- result list.+--+-- >>> captureNames (compile "(?<one>abc) (def) (?<three>ghi)")+-- [("one", 0), ("three", 2)]+captureNames :: Regex -> [(S.ByteString, Int)]+captureNames (Regex pcre_fp _) = unsafePerformIO $+ withForeignPtr pcre_fp $ \pcre_ptr -> do+ count <- fullInfoInt pcre_ptr info_namecount+ entrysize <- fullInfoInt pcre_ptr info_nameentrysize++ buf <- alloca $ \n_ptr -> do+ c_pcre_fullinfo pcre_ptr nullPtr info_nametable n_ptr+ buf <- peek n_ptr+ S.packCStringLen (buf, count*entrysize)++ let results = split entrysize buf+ zeroIndexed = fmap (subtract 1) <$> results+ sorted = sortBy (compare `on` snd) zeroIndexed+ return sorted++ where+ -- Split the nametable buffer into entries. Each entry has a fixed size in+ -- bytes. The first two bytes in each entry store the pattern number in+ -- big-endian format, the bytes following that contain the nul-terminated+ -- name of the subpattern.+ split :: Int -> S.ByteString -> [(S.ByteString, Int)]+ split entrysize buf+ | S.null buf = []+ | otherwise =+ let+ (entry, tail) = S.splitAt entrysize buf+ idx = fromIntegral . S.index entry+ num = idx 0 * 256 + idx 1+ name = S.takeWhile (/= 0) $ S.drop 2 entry+ in (name, num) : split entrysize tail
Text/Regex/PCRE/Light/Base.hsc view
@@ -1,4 +1,11 @@-{-# LANGUAGE CPP, ForeignFunctionInterface, GeneralizedNewtypeDeriving #-}+{-# LANGUAGE CPP, ForeignFunctionInterface, CApiFFI #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++#if __GLASGOW_HASKELL__ < 910+-- Disable dodgy-foreign-imports warnings.+-- See https://gitlab.com/daniel-casanueva/pcre-light/-/merge_requests/17+{-# OPTIONS_GHC -Wno-dodgy-foreign-imports #-}+#endif -------------------------------------------------------------------- -- | -- Module : Text.Regex.PCRE.Light.Base@@ -25,6 +32,7 @@ , c_pcre_compile , c_pcre_exec , c_pcre_fullinfo+ , c_pcre_free ------------------------------------------------------------------------ @@ -823,4 +831,8 @@ -> PCREInfo -> Ptr a -> IO CInt++-- | Return pcre_free finalizer+foreign import capi "pcre.h value pcre_free"+ c_pcre_free :: FinalizerPtr a
Text/Regex/PCRE/Light/Char8.hs view
@@ -9,7 +9,7 @@ -- Portability: H98 + FFI -- ----------------------------------------------------------------------- +-- -- A simple, portable binding to perl-compatible regular expressions -- (PCRE) via 8-bit latin1 Strings. --@@ -22,6 +22,8 @@ -- * String interface , compile, compileM , match+ , S.captureCount+ , captureNames -- * Regex types and constructors externally visible @@ -69,7 +71,7 @@ import qualified Data.ByteString.Char8 as S import qualified Text.Regex.PCRE.Light as S-import Text.Regex.PCRE.Light hiding (match, compile, compileM)+import Text.Regex.PCRE.Light hiding (match, compile, compileM, captureNames) -- | 'compile' --@@ -77,7 +79,7 @@ -- The arguments are: -- -- * 'pat': A ByteString, which may or may not be zero-terminated,--- containing the regular expression to be compiled. +-- containing the regular expression to be compiled. -- -- * 'flags', optional bit flags. If 'Nothing' is provided, defaults are used. --@@ -125,15 +127,15 @@ -- -- * 'no_utf8_check' - Do not check the pattern for UTF-8 validity ----- If compilation of the pattern fails, the 'Left' constructor is +-- If compilation of the pattern fails, the 'Left' constructor is -- returned with the error string. Otherwise an abstract type -- representing the compiled regular expression is returned. -- The regex is allocated via malloc on the C side, and will be -- deallocated by the runtime when the Haskell value representing it -- goes out of scope. ----- As regexes are often defined statically, GHC will compile them --- to null-terminated, strict C strings, enabling compilation of the +-- As regexes are often defined statically, GHC will compile them+-- to null-terminated, strict C strings, enabling compilation of the -- pattern without copying. This may be useful for very large patterns. -- -- See man pcreapi for more details.@@ -203,3 +205,15 @@ Nothing -> Nothing Just x -> Just (map S.unpack x) {-# INLINE match #-}+++-- | 'captureNames'+--+-- Returns the names and numbers of all named subpatterns in the regular+-- expression. Groups are zero-indexed. Unnamed groups are counted, but don't appear in the+-- result list.+--+-- >>> captureNames (compile "(?<one>abc) (def) (?<three>ghi)")+-- [("one", 0), ("three", 2)]+captureNames :: Regex -> [(String, Int)]+captureNames r = map (\(n,i) -> (S.unpack n, i)) $ S.captureNames r
− configure
@@ -1,9 +0,0 @@-#!/bin/sh-#--# subst standard header path variables-if test -n "$CPPFLAGS" ; then- echo "Found CPPFLAGS in environment: '$CPPFLAGS'"- sed 's,@CPPFLAGS@,'"$CPPFLAGS"',g;s,@LDFLAGS@,'"$LDFLAGS"',g' \- < pcre-light.buildinfo.in > pcre-light.buildinfo-fi
− pcre-light.buildinfo.in
@@ -1,3 +0,0 @@-ghc-options: -optc@CPPFLAGS@-cc-options: @CPPFLAGS@-ld-options: @LDFLAGS@
pcre-light.cabal view
@@ -1,43 +1,55 @@-name: pcre-light-version: 0.3.1.1-homepage: http://code.haskell.org/~dons/code/pcre-light-synopsis: A small, efficient and portable regex library for Perl 5 compatible regular expressions+name: pcre-light+version: 0.4.1.3+homepage: https://gitlab.com/daniel-casanueva/haskell/pcre-light+synopsis: Portable regex library for Perl 5 compatible regular expressions description:- A small, efficient and portable regex library for Perl 5 compatible regular expressions+ A small, efficient and portable regex library for Perl 5 compatible regular expressions. . The PCRE library is a set of functions that implement regular expression pattern matching using the same syntax and semantics as Perl 5. .- Test coverage data for this library is available at:- <http://code.haskell.org/~dons/tests/pcre-light/hpc_index.html>+ If installation fails with missing pcre/pkg-config, try installing+ the @libpcre3-dev@ package (linux) or running @brew install pcre pkg-config@ (macOS). .-category: Text-license: BSD3-license-file: LICENSE-copyright: (c) 2007-2010. Don Stewart <dons@galois.com>-author: Don Stewart-maintainer: Don Stewart <dons@galois.com>-cabal-version: >= 1.2.0-build-type: Configure-tested-with: GHC ==6.8.2, GHC ==6.6.1, GHC ==6.12.1, Hugs ==2005-extra-source-files: configure, pcre-light.buildinfo.in-extra-tmp-files: pcre-light.buildinfo+category: Text+license: BSD3+license-file: LICENSE+copyright: (c) 2007-2010. Don Stewart <dons@galois.com>+author: Don Stewart+maintainer: Daniel Casanueva <coding `at` danielcasanueva.eu>+bug-reports: https://codeberg.org/daniel-casanueva/pcre-light+cabal-version: 1.18+build-type: Simple+extra-doc-files: README.md, ChangeLog.md -flag small_base- description: Build with new smaller base library- default: False+flag use-pkg-config+ default: False+ manual: True library exposed-modules: Text.Regex.PCRE.Light Text.Regex.PCRE.Light.Char8 Text.Regex.PCRE.Light.Base - extensions: CPP, ForeignFunctionInterface+ default-language: Haskell2010+ default-extensions: CPP, ForeignFunctionInterface - if flag(small_base)- build-depends: base >= 3 && <= 5, bytestring >= 0.9- else- build-depends: base < 3+ build-depends: base >= 3 && <= 5, bytestring >= 0.9 - extra-libraries: pcre+ if flag(use-pkg-config)+ pkgconfig-depends: libpcre+ else+ extra-Libraries: pcre +test-suite unit+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: tests+ main-is: Unit.hs+ build-depends: + base >= 3 && <= 5+ , bytestring >= 0.9+ , containers >= 0.5.5.1+ , HUnit >= 1.2.5.2+ , mtl >= 2.1.3.2+ , pcre-light
− tests/Makefile
@@ -1,11 +0,0 @@-all:- ghc -Onot -fasm Unit.hs --make && ./Unit-# runhaskell Unit.hs && echo $$?--test::- ghc -ddump-simpl-stats -lpcre -fhpc --make -i.. -i../dist/build/ Unit.hs -o Unit -O2 -no-recomp- rm -f *.tix- ./Unit - hpc report --decl-list --exclude=Main Unit- hpc markup --fun-entry-count --exclude=Main Unit-
− tests/Parse.hs
@@ -1,75 +0,0 @@------ A script to translate the pcre.c testsuite into Haskell-----import System.Environment-import System.IO-import Data.Char-import Data.List-import Text.PrettyPrint.HughesPJ--data Test = Test String [String] [Maybe [String]]- deriving (Eq,Show,Read)--main = do- [f,g] <- getArgs- inf <- readFile f- outf <- readFile g-- let in_str = lines inf- out_str = lines outf-- let loop [] [] = []- loop i_xs o_xs = Test r subj results : loop (dropWhile (=="") i_ys)- (dropWhile (=="") o_ys)- where- ((r:subj), i_ys) = break (== "") i_xs-- ((_:results'),o_ys) = break (== "") o_xs- results= [ if s == "No match"- then Nothing- else Just [s]- | s <- filter (not . all isSpace . take 2) results'- ]-- print . vcat . intersperse (char ',') . map ppr . loop in_str $ out_str--breakReg ('/':rest) = - let s = reverse . dropWhile (/= '/') . reverse $ rest-- t = case head (reverse rest) of- 'i' -> ["caseless"]- '/' -> []- _ -> ["ERROR"]-- in if s == "" then ("ERROR", [])- else (init s, t)---breakReg ('"':rest) =- let s = reverse . dropWhile (/= '"') . reverse $ rest-- t = case head (reverse rest) of- 'i' -> ["caseless"]- '/' -> []- _ -> ["ERROR"]-- in if s == "" then ("ERROR", [])- else (init s, t)--breakReg s = ("ERROR", [])--ppr :: Test -> Doc-ppr (Test r subjs res) =- hang (empty <+> text "testRegex" <+> text- (show (fst $ breakReg r)) <+> - bracket (case snd (breakReg r) of- [] -> empty- [x] -> text x- ))- 4 $- (bracket $ vcat $ punctuate (char ',') (map (text.show) subjs))- $+$- (bracket $ vcat $ punctuate (char ',') (map (text.show) res))--bracket x = char '[' <> x <> char ']'
tests/Unit.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE OverloadedStrings #-} -import Text.Regex.PCRE.Light (compile,compileM,match)+import Text.Regex.PCRE.Light (compile,compileM,match,captureNames,captureCount) import qualified Text.Regex.PCRE.Light.Char8 as String (compile,compileM,match)-import Text.Regex.PCRE.Light.Base +import Text.Regex.PCRE.Light.Base import qualified Data.ByteString.Char8 as S import System.IO@@ -16,17 +16,17 @@ import System.IO.Unsafe import Control.Exception-import Control.Monad.Error+import Control.Monad.Except +assertBool' :: S.ByteString -> Bool -> Assertion assertBool' s = assertBool (S.unpack s)++assertEqual' :: (Eq a, Show a) => S.ByteString -> a -> a -> Assertion assertEqual' s = assertEqual (S.unpack s) +testLabel :: S.ByteString -> Test -> Test testLabel s = TestLabel (S.unpack s) -instance Error S.ByteString where- noMsg = S.empty- strMsg = S.pack- testRegex :: S.ByteString -> [PCREOption] -> [S.ByteString]@@ -67,9 +67,23 @@ | (i,o) <- zip (map (S.unpack) inputs) (map (fmap (map S.unpack)) outputs) ] +testCaptures :: Int+ -> S.ByteString+ -> [(S.ByteString, Int)]+ -> Test+testCaptures expectedTotalCaptures regex expectedCaptureNames = testLabel regex $+ TestCase $ do+ r <- case compileM regex [] of+ Left s -> assertFailure ("ERROR in ByteString in compileM " ++ s)+ Right r -> return r+ assertEqual' "Capture group name mismatch" expectedCaptureNames (captureNames r)+ assertEqual' "Capture count mismatch" expectedTotalCaptures (captureCount r)++main :: IO () main = do counts <- runTestTT tests when (errors counts > 0 || failures counts > 0) exitFailure +tests :: Test tests = TestList [ testRegex "the quick brown fox" []@@ -89,12 +103,11 @@ Left ("nothing to repeat" ) == compileM "*" []) , testLabel "compile failure" $- TestCase $ (assertBool' "compile failure" =<< (return $- (Just ("Text.Regex.PCRE.Light: Error in regex: nothing to repeat"))- ==- (unsafePerformIO $ do- handle (\e -> return (Just (S.pack $ show e)))- (compile "*" [] `seq` return Nothing))))+ TestCase $ (assertEqual' "compile failure"+ (Just ("Text.Regex.PCRE.Light: Error in regex: nothing to repeat"))+ (fmap (head . S.lines) . unsafePerformIO $ do+ handle (\e -> return (Just (S.pack $ displayException (e :: SomeException))))+ (compile "*" [] `seq` return Nothing))) -- , testRegex "\0*" [] -- the embedded null in the pattern seems to be a problem -- ["\0\0\0\0"]@@ -1530,6 +1543,7 @@ Nothing, Nothing] +{- , testRegex "^(a\\1?){4}$" [] ["a", "aa",@@ -1561,6 +1575,7 @@ Nothing, Nothing, Nothing]+-} , testRegex "abc" [] ["abc",@@ -3588,7 +3603,7 @@ [Just ["b", "b"]] , -+{- testRegex "(?(1)a|b)" [] [] []@@ -3600,6 +3615,7 @@ [Just ["a"]] , +-} testRegex "(x)?(?(1)a|b)" [] ["*** Failers",@@ -3655,7 +3671,7 @@ "*** Failers", "blah)", "(blah"]- [Just ["(blah)", "(", ")"], + [Just ["(blah)", "(", ")"], Just ["blah"], Nothing, Nothing,@@ -3891,21 +3907,6 @@ [Just ["", ""]] , --- testRegex "^[a-\\d]" []- ["abcde",- "-things",- "0digit",- "*** Failers",- "bcdef "]- [Just ["a"],- Just ["-"],- Just ["0"],- Nothing,- Nothing]- ,- testRegex "\\Qabc\\$xyz\\E" [] ["abc\\$xyz"] [Just ["abc\\$xyz"]]@@ -4450,5 +4451,50 @@ Nothing, Nothing] + , -- Regression test; ensure "" and empty are treated the same.+ testRegex ".*" []+ [ ""+ , S.empty]+ [Just [""]+ ,Just [""]]++ -- named capture group tests+ , -- Handles cases with no capture groups+ testCaptures 0 "no capture groups" []++ , -- Properly labels capture groups+ testCaptures 1 "(?<first>first capture group)" [("first", 0)]++ , -- Doesn't return labels for unnamed groups+ testCaptures 1 "(doesn't return unnamed groups)" []++ , -- Counts but doesn't return unnamed groups+ testCaptures 3 "(?<one>abc) (def) (?<three>ghi)" [("one", 0), ("three", 2)]++ , -- Doesn't count non-capturing groups+ testCaptures 1 "(?:abc) (?<named>def)" [("named", 0)]++ , -- Doesn't count named back-references+ testCaptures 2 "(?<first>abc) (?P=first) (?<second>def)" [("first", 0), ("second", 1)]++ , -- Test alternate group naming syntaxes+ testCaptures 3 "(?'first'abc) (?P<second>def) (?<third>ghi)" [("first", 0), ("second", 1), ("third", 2)]++ , -- Groups are returned in numeric order+ testCaptures 3 "(?'c'0) (?P<b>1) (?<a>2)" [("c", 0), ("b", 1), ("a", 2)]++ , -- Handles alternation between groups+ testCaptures 2 "(?<optionA>abc)|(?<optionB>def)" [("optionA", 0), ("optionB", 1)]++ , -- Labels optional groups+ testCaptures 2 "(?<named>abc)(?<optional>def)?" [("named", 0), ("optional", 1)]++ , -- Handles nested named groups+ testCaptures 3 "(?<named>a(?<nested>b)c) (?<unnested>d)" [("named", 0), ("nested", 1), ("unnested", 2)]++ , testLabel "compile failure on duplicate named groups" $+ TestCase $ (assertEqual' "compile failure on duplicate named groups"+ (Left ("two named subpatterns have the same name"))+ (compileM "(?<dup>abc) (?<dup>def)" [])) ]
− tests/failure1.hs
@@ -1,5 +0,0 @@-import Text.Regex.PCRE.Light.Char8--main = do- let r = compile "(a|)*\\d" []- print (match r "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" [])