packages feed

pcre2 1.0.2 → 1.1.0

raw patch · 8 files changed

+166/−70 lines, 8 filesdep +transformersPVP ok

version bump matches the API change (PVP)

Dependencies added: transformers

API changes (from Hackage documentation)

- Text.Regex.Pcre2: capturesOptA :: Alternative f => Option -> Text -> Text -> f (NonEmpty Text)
+ Text.Regex.Pcre2: capturesAOpt :: Alternative f => Option -> Text -> Text -> f (NonEmpty Text)
+ Text.Regex.Pcre2: capturesAll :: Text -> Text -> [NonEmpty Text]
+ Text.Regex.Pcre2: capturesAllOpt :: Option -> Text -> Text -> [NonEmpty Text]
+ Text.Regex.Pcre2: matchAll :: Text -> Text -> [Text]
+ Text.Regex.Pcre2: matchAllOpt :: Option -> Text -> Text -> [Text]

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Changelog and Acknowledgements +## 1.1.0+* Added global matching.+    * New functions `matchAll`, `matchAllOpt`, `capturesAll`, `capturesAllOpt`.+    * Changed all traversals from affine to non-affine.+* Changed `capturesOptA` to `capturesAOpt` for naming consistency.+ ## 1.0.2 * Fixed [#4](https://github.com/sjshuck/pcre2/4), where multiple named captures   were not type-indexed correctly.
README.md view
@@ -53,7 +53,6 @@   10.35), with a complete, exposed Haskell binding.  ## TODO-* Global matching.  (We already have global substitution.) * Many performance optimizations.  Currently we are 2–3× slower than   other libraries doing most things (order of a few μs). * Make use of DFA and JIT compilation.
bench/Bench.hs view
@@ -68,12 +68,21 @@             in capture @"bar" cs],      bgroup "substitutions" [-        bench "PCRE2-native" $ flip nf textSubject $-            sub (Text.pack "(?<=foo )bar(?= baz)") (Text.pack "quux"),+        bgroup "single" [+            bench "PCRE2-native" $ flip nf textSubject $+                sub (Text.pack "(?<=foo )bar(?= baz)") (Text.pack "quux"), -        let quux = Text.pack "quux"-        in bench "lens-powered" $ flip nf textSubject $-            set ([_regex|foo (bar) baz|] . _capture @1) quux]]+            let quux = Text.pack "quux"+            in bench "lens-powered" $ flip nf textSubject $+                set ([_regex|foo (bar) baz|] . _capture @1) quux],++        let fruit = Text.pack "apples and bananas"+        in bgroup "multiple" [+            let a2o = gsub (Text.pack "a") (Text.pack "o")+            in bench "PCRE2-native" $ nf a2o fruit,++            let a2o = set [_regex|a|] (Text.pack "o")+            in bench "lens-powered" $ nf a2o fruit]]]  stringPattern = "foo (bar) baz" stringSubject = "foo bar baz"
pcre2.cabal view
@@ -4,16 +4,16 @@ -- -- see: https://github.com/sol/hpack ----- hash: 9e2fa347c266eb48c8d78b48cae7cbadf7ea4bf1414ecafc9fbaeb5e2a7c492c+-- hash: 0a7a573e61659cfbca9092c36ae2549d63b0c6745b4bc3953f29dfc97aebd1b6  name:           pcre2-version:        1.0.2+version:        1.1.0 synopsis:       Regular expressions via the PCRE2 C library (included) description:    Please see the README on GitHub at <https://github.com/sjshuck/hs-pcre2> category:       Text homepage:       https://github.com/sjshuck/hs-pcre2#readme bug-reports:    https://github.com/sjshuck/hs-pcre2/issues-author:         Shlomo Shuck+author:         Shlomo Shuck and contributors maintainer:     stevenjshuck@gmail.com copyright:      2020 Shlomo Shuck license:        Apache-2.0@@ -149,6 +149,7 @@     , mtl     , template-haskell     , text+    , transformers   default-language: Haskell2010  test-suite pcre2-test@@ -168,6 +169,7 @@     , pcre2     , template-haskell     , text+    , transformers   default-language: Haskell2010  benchmark pcre2-benchmarks@@ -189,4 +191,5 @@     , regex-pcre-builtin     , template-haskell     , text+    , transformers   default-language: Haskell2010
src/hs/Text/Regex/Pcre2.hs view
@@ -138,12 +138,16 @@     -- ** Basic matching functions     match,     matchOpt,+    matchAll,+    matchAllOpt,     matches,     matchesOpt,     captures,     capturesOpt,     capturesA,-    capturesOptA,+    capturesAOpt,+    capturesAll,+    capturesAllOpt,      -- ** PCRE2-native substitution     sub,@@ -176,7 +180,7 @@     -- > _nee :: Traversal' Text Text     -- > _nee = _match "(?i)\\bnee\\b"     ---    -- In addition to getting results, they support substitution through+    -- In addition to getting results, they support global substitution through     -- setting; more generally, they can accrete effects while performing     -- replacements.     --@@ -185,12 +189,6 @@     -- "NEE"     -- NOO     -- "We are the knights who say...NOO!"-    -- >>>-    ---    -- The optic interface signals match failure by not targeting anything.-    ---    -- >>> promptNee "Shhhhh"-    -- "Shhhhh"     -- >>>     --     -- In general these traversals are not law-abiding.
src/hs/Text/Regex/Pcre2/Internal.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE PolyKinds #-}@@ -17,6 +18,8 @@ import           Control.Applicative        (Alternative(..)) import           Control.Exception          hiding (TypeError) import           Control.Monad.State.Strict+import           Control.Monad.Trans.Maybe  (MaybeT(..))+import           Control.Monad.Writer.Lazy  (WriterT(..), execWriterT, tell) import           Data.Either                (partitionEithers) import           Data.Function              ((&)) import           Data.Functor               ((<&>))@@ -29,12 +32,12 @@ import           Data.List.NonEmpty         (NonEmpty(..)) import qualified Data.List.NonEmpty         as NE import           Data.Maybe                 (fromJust, fromMaybe)-import           Data.Monoid                (Alt(..), Any(..), First(..))+import           Data.Monoid import           Data.Proxy                 (Proxy(..)) import           Data.Text                  (Text) import qualified Data.Text                  as Text import qualified Data.Text.Foreign          as Text-import           Data.Type.Bool             (type (||), If)+import           Data.Type.Bool             (If, type (||)) import           Data.Type.Equality         (type (==)) import           Data.Typeable              (cast) import           Foreign@@ -42,7 +45,7 @@ import qualified Foreign.Concurrent         as Conc import           GHC.TypeLits               hiding (Text) import qualified GHC.TypeLits               as TypeLits-import           System.IO.Unsafe           (unsafePerformIO)+import           System.IO.Unsafe import           Text.Regex.Pcre2.Foreign  -- * General utilities@@ -136,6 +139,7 @@ view l = getConst . l Const to k f = Const . getConst . f . k has l = getAny . getConst . l (\_ -> Const $ Any True)+toListOf l = flip appEndo [] . view (l . to (Endo . (:)))  -- toAlternativeOf :: (Alternative f) => Getting (Alt f a) s a -> s -> f a toAlternativeOf l = getAlt . view (l . to (Alt . pure))@@ -146,9 +150,10 @@ -- * Assembling inputs into @Matcher@s and @Subber@s  -- | A matching function where all inputs and auxilliary data structures have--- been \"compiled\".  It takes a subject and produces a raw result code and--- match data, to be passed to further validation and inspection.-type Matcher = Text -> IO (CInt, MatchData)+-- been \"compiled\".  It takes a subject and produces a list of match data+-- objects, representing a global match, to be passed to further validation and+-- inspection.+type Matcher = Text -> IO [MatchData]  -- | A substitution function.  It takes a subject and produces the number of -- substitutions performed (0 or 1, or more in the presence of `SubGlobal`)@@ -679,26 +684,55 @@     -> IO Matcher assembleMatcher = assembleSubjFun $ \matchEnv@(MatchEnv {..}) ->     let matchTempEnvWithSubj = mkMatchTempEnv matchEnv-    in \subject -> withForeignPtr matchEnvCode $ \codePtr -> do-        matchData <- mkForeignPtr pcre2_match_data_free $-            pcre2_match_data_create_from_pattern codePtr nullPtr+    in \subject ->+        withForeignPtr matchEnvCode $ \codePtr ->+        Text.useAsPtr subject $ \subjPtr subjCUs -> do+            MatchTempEnv {..} <- matchTempEnvWithSubj subject -        MatchTempEnv {..} <- matchTempEnvWithSubj subject-        result <--            Text.useAsPtr subject $ \subjPtr subjCUs ->-            withForeignOrNullPtr matchTempEnvCtx $ \ctxPtr ->-            withForeignPtr matchData $ \matchDataPtr -> pcre2_match-                codePtr-                (castCUs subjPtr)-                (fromIntegral subjCUs)-                0-                matchEnvOpts-                matchDataPtr-                ctxPtr-        maybeRethrow matchTempEnvRef+            -- Loop over the subject, emitting MatchData's until stopping.+            execWriterT $ runMaybeT $ fix1 0 $ \continue curOff -> do+                -- TODO Can one of these be reused for a whole global match?+                matchData <- liftIO $ mkForeignPtr pcre2_match_data_free $+                    pcre2_match_data_create_from_pattern codePtr nullPtr -        return (result, matchData)+                result <- liftIO $+                    withForeignOrNullPtr matchTempEnvCtx $ \ctxPtr ->+                    withForeignPtr matchData $ \matchDataPtr -> pcre2_match+                        codePtr+                        (castCUs subjPtr)+                        (fromIntegral subjCUs)+                        curOff+                        matchEnvOpts+                        matchDataPtr+                        ctxPtr +                -- Handle no match and errors+                when (result == pcre2_ERROR_NOMATCH) stop+                when (result == pcre2_ERROR_CALLOUT) $+                    liftIO $ maybeRethrow matchTempEnvRef+                liftIO $ check (> 0) result++                emit matchData++                lazy $ do+                    -- Determine next starting offset+                    nextOff <- liftIO $+                        withForeignPtr matchData $ \matchDataPtr -> do+                            ovecPtr <- pcre2_get_ovector_pointer matchDataPtr+                            curOffEnd <- peekElemOff ovecPtr 1+                            -- Prevent infinite loop upon empty match+                            return $ max curOffEnd (curOff + 1)++                    -- Handle end of subject+                    when (nextOff > fromIntegral subjCUs) stop++                    continue nextOff++    where+    stop = MaybeT $ return Nothing+    emit x = tell [x]+    lazy = MaybeT . WriterT . unsafeInterleaveIO . runWriterT . runMaybeT+ -- | A `Subber` works by first writing results to a reasonably-sized buffer.  If -- we run out of room, PCRE2 allows us to simulate the rest of the substitution -- without writing anything, in order to calculate how big the buffer actually@@ -984,7 +1018,7 @@ -- * Packaging @Matcher@s and @Subber@s as public API functions  -- | The most general form of a matching function, which can also be used as a--- @Setter'@ to perform substitutions at the Haskell level.+-- @Setter'@ to perform substitutions at the Haskell level.  Operates globally. -- -- When any capture is forced, all captures are forced in order to GC C data -- more promptly.  When we need /some/ captures but not /all/ captures, we can@@ -993,22 +1027,23 @@ -- -- Internal only!  Users should not (have to) know about `Matcher`. _capturesInternal :: Matcher -> [Int] -> Traversal' Text (NonEmpty Text)-_capturesInternal matcher whitelist f subject-    | result == pcre2_ERROR_NOMATCH = pure subject-    | result <= 0                   = throw $ Pcre2Exception result-    | otherwise                     = f cs <&> \cs' ->-        -- Swag foldl-as-foldr to create only as many segments as we need to-        -- stitch back together and no more.-        let triples = ovecEntries `NE.zip` cs `NE.zip` cs'-        in Text.concat $ foldr mkSegments termSegments triples 0+_capturesInternal matcher whitelist f subject = traverse f css <&> \css' ->+    -- Swag foldl-as-foldr to create only as many segments as we need to+    -- stitch back together and no more.+    let triples = zip3 (flatten ovecEntries) (flatten css) (flatten css')+    in Text.concat $ foldr mkSegments termSegments triples 0      where -    (result, matchData) = unsafePerformIO $ matcher subject-    cs = unsafePerformIO $ forM ovecEntries $ evaluate . slice subject-    ovecEntries = unsafePerformIO $ getOvecEntriesAt whitelist matchData+    flatten = concatMap NE.toList+    ovecEntries = do+        matchData <- unsafePerformIO $ matcher subject+        return $ unsafePerformIO $ getOvecEntriesAt whitelist matchData+    css = do+        ovecEntry <- ovecEntries+        return $ unsafePerformIO $ mapM (evaluate . slice subject) ovecEntry -    mkSegments ((SliceRange off offEnd, c), c') r prevOffEnd+    mkSegments (SliceRange off offEnd, c, c') r prevOffEnd         | off == fromIntegral pcre2_UNSET || c == c' =             -- This substring is unset or unchanged.  Keep going without making             -- cuts.@@ -1047,8 +1082,8 @@ withMatcher :: (Matcher -> a) -> Option -> Text -> a withMatcher f option patt = f $ unsafePerformIO $ assembleMatcher option patt --- | Match a pattern to a subject and return a list of captures, or @[]@ if no--- match.+-- | Match a pattern to a subject once and return a list of captures, or @[]@ if+-- no match. captures :: Text -> Text -> [Text] captures = capturesOpt mempty @@ -1056,22 +1091,38 @@ capturesOpt :: Option -> Text -> Text -> [Text] capturesOpt option patt = view $ _capturesOpt option patt . to NE.toList --- | Match a pattern to a subject and return a non-empty list of captures in an--- `Alternative`, or `empty` if no match.  The non-empty list constructor `:|`--- serves as a cue to differentiate the 0th capture from the others:+-- | Match a pattern to a subject once and return a non-empty list of captures+-- in an `Alternative`, or `empty` if no match.  The non-empty list constructor+-- `:|` serves as a cue to differentiate the 0th capture from the others: -- -- > let parseDate = capturesA "(\\d{4})-(\\d{2})-(\\d{2})" -- > in case parseDate "submitted 2020-10-20" of -- >     Just (date :| [y, m, d]) -> ... -- >     Nothing                  -> putStrLn "didn't match" capturesA :: (Alternative f) => Text -> Text -> f (NonEmpty Text)-capturesA = capturesOptA mempty+capturesA = capturesAOpt mempty --- | @capturesOptA mempty = capturesA@-capturesOptA :: (Alternative f) => Option -> Text -> Text -> f (NonEmpty Text)-capturesOptA option patt = toAlternativeOf $ _capturesOpt option patt+-- | @capturesAOpt mempty = capturesA@+--+-- @since 1.1.0+capturesAOpt :: (Alternative f) => Option -> Text -> Text -> f (NonEmpty Text)+capturesAOpt option patt = toAlternativeOf $ _capturesOpt option patt --- | Does the pattern match the subject?+-- | Match a pattern to a subject and lazily return zero or more non-empty lists+-- of captures corresponding to every non-overlapping place in the subject the+-- pattern matched.+--+-- @since 1.1.0+capturesAll :: Text -> Text -> [NonEmpty Text]+capturesAll = capturesAllOpt mempty++-- | @capturesAllOpt mempty = capturesAll@+--+-- @since 1.1.0+capturesAllOpt :: Option -> Text -> Text -> [NonEmpty Text]+capturesAllOpt option patt = toListOf $ _capturesOpt option patt++-- | Does the pattern match the subject at least once? matches :: Text -> Text -> Bool matches = matchesOpt mempty @@ -1079,8 +1130,8 @@ matchesOpt :: Option -> Text -> Text -> Bool matchesOpt = withMatcher $ \matcher -> has $ _capturesInternal matcher [] --- | Match a pattern to a subject and return the portion that matched in an--- @Alternative@, or `empty` if no match.+-- | Match a pattern to a subject once and return the portion that matched in an+-- `Alternative`, or `empty` if no match. match :: (Alternative f) => Text -> Text -> f Text match = matchOpt mempty @@ -1088,6 +1139,19 @@ matchOpt :: (Alternative f) => Option -> Text -> Text -> f Text matchOpt option patt = toAlternativeOf $ _matchOpt option patt +-- | Match a pattern to a subject and lazily return a list of all+-- non-overlapping portions that matched.+--+-- @since 1.1.0+matchAll :: Text -> Text -> [Text]+matchAll = matchAllOpt mempty++-- | @matchAllOpt mempty = matchAll@+--+-- @since 1.1.0+matchAllOpt :: Option -> Text -> Text -> [Text]+matchAllOpt option patt = toListOf $ _matchOpt option patt+ -- | Perform at most one substitution.  See -- [the docs](https://pcre.org/current/doc/html/pcre2api.html#SEC36) for the -- special syntax of /replacement/.@@ -1116,8 +1180,8 @@ subOpt option patt replacement = snd . unsafePerformIO . subber where     subber = unsafePerformIO $ assembleSubber replacement option patt --- | Given a pattern, produce an affine traversal (0 or 1 targets) that focuses--- from a subject to a potential non-empty list of captures.+-- | Given a pattern, produce a traversal (0 or more targets) that focuses from+-- a subject to each non-empty list of captures that pattern matches globally. -- -- Substitution works in the following way:  If a capture is set such that the -- new `Text` is not equal to the old one, a substitution occurs, otherwise it@@ -1160,8 +1224,8 @@ _capturesOpt :: Option -> Text -> Traversal' Text (NonEmpty Text) _capturesOpt = withMatcher $ \matcher -> _capturesInternal matcher [0 ..] --- | Given a pattern, produce an affine traversal (0 or 1 targets) that focuses--- from a subject to the portion of it that matches.+-- | Given a pattern, produce a traversal (0 or more targets) that focuses from+-- a subject to the portions of it that match. -- -- Equivalent to @\\patt -> `_captures` patt . ix 0@, but more efficient. _match :: Text -> Traversal' Text Text
src/hs/Text/Regex/Pcre2/TH.hs view
@@ -133,7 +133,7 @@      quoteDec = const $ fail "regex: cannot produce declarations"} --- | A lens variant of `regex`.  Can only be used as an expression.+-- | A global, optical variant of `regex`.  Can only be used as an expression. -- -- > _regex :: String -> Traversal' Text (Captures info) -- > _regex :: String -> Traversal' Text Text
test/Spec.hs view
@@ -189,6 +189,23 @@         it "permits other captures to be changed via Traversal'" $ do             ("" & [_regex|(a)?|] . _capture @0 .~ "foo") `shouldBe` "foo" +    describe "Traversal'" $ do+        it "supports global substitutions" $ do+            ("apples and bananas" & _match "a" .~ "o")+                `shouldBe` "opples ond bononos"++        it "is lazy" $ do+            counter <- newIORef (0 :: Int)+            let callout = UnsafeCallout $ \_ -> do+                    modifyIORef counter (+ 1)+                    return CalloutProceed+            take 3 ("apples and bananas" ^.. _matchOpt callout "(?C1)a")+                `shouldBe` ["a", "a", "a"]+            readIORef counter `shouldReturn` 3++        it "converges in the presence of empty matches" $ do+            length (toListOf [_regex||] "12345") `shouldBe` 6+     describe "bug fixes" bugFixes  bugFixes :: Spec