pcre2 2.3.0 → 2.3.1
raw patch · 9 files changed
+131/−120 lines, 9 filesdep +tasty-benchdep −criterion
Dependencies added: tasty-bench
Dependencies removed: criterion
Files
- ChangeLog.md +4/−0
- README.md +23/−20
- bench/Bench.hs +12/−13
- pcre2.cabal +7/−7
- src/hs/Text/Regex/Pcre2.hs +2/−2
- src/hs/Text/Regex/Pcre2/Internal.hs +25/−27
- src/hs/Text/Regex/Pcre2/TH.hs +28/−32
- src/hs/Text/Regex/Pcre2/Unsafe.hs +2/−2
- test/Spec.hs +28/−17
ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog and Acknowledgements +## 2.3.1+* Fixed [#52](https://github.com/sjshuck/hs-pcre2/issues/52) where the caret+ appeared in the wrong place.+ ## 2.3.0 * Disabled building JIT by default. * Removed various JIT-related functions and constants from
README.md view
@@ -7,11 +7,14 @@ ## Teasers ```haskell-licensePlate :: Text -> Maybe Text-licensePlate = match "[A-Z]{3}[0-9]{3,4}"+licensePlateRegex :: (Alternative f) => Text -> f Text+licensePlateRegex = match "[A-Z]{3}[0-9]{3,4}" -licensePlates :: Text -> [Text]-licensePlates = match "[A-Z]{3}[0-9]{3,4}"+firstLicensePlate :: Text -> Maybe Text+firstLicensePlate = licensePlateRegex++allLicensePlates :: Text -> [Text]+allLicensePlates = licensePlateRegex ``` ```haskell case "The quick brown fox" of@@ -31,16 +34,21 @@ \s*$ # Ignore trailing whitespace |] -forMOf kv'd file $ execStateT $ do- k <- gets $ capture @1- v <- gets $ capture @2- liftIO $ Text.putStrLn $ "found " <> k <> " set to " <> v+forOf kv'd file $ \kv -> do+ let k = capture @1 kv+ v = capture @2 kv - case myMap ^. at k of+ Text.putStrLn $ "found " <> k <> " set to " <> v++ kv' <- case Map.lookup k replacements of Just v' | v /= v' -> do- liftIO $ Text.putStrLn $ "setting " <> k <> " to " <> v'- _capture @2 .= v'- _ -> liftIO $ Text.putStrLn $ "no change for " <> k+ Text.putStrLn $ "setting " <> k <> " to " <> v'+ return $ set (_capture @2) v' kv+ _ -> do+ Text.putStrLn $ "no change for " <> k+ return kv++ ... ``` ## Features@@ -49,7 +57,6 @@ `-> result`. * No [custom typeclasses](https://hackage.haskell.org/package/regex-base/docs/Text-Regex-Base-RegexLike.html#t:RegexContext). * A single datatype for both compile and match options, the `Option` monoid.-* UTF-8 `Text` everywhere. * Match success expressed via `Alternative`. * Opt-in Template Haskell facilities for compile-time verification of patterns, indexing captures, and memoizing inline regexes.@@ -66,14 +73,9 @@ complete, exposed Haskell binding. ## Performance-Currently we are slower than other libraries. The difference is less pronounced-for `Text` where no conversions are necessary to use this library.+Currently (2026-05-02) we are much slower than other libraries. -| Operation | `pcre2` | `regex-pcre-builtin` | `pcre-light` |-| :-- | --: | --: | --: |-| Matching a `String` | 1.22 μs | 0.34 μs | 0.23 μs |-| Matching a `Text` (shorter) | 1.22 μs | 0.93 μs | 0.29 μs |-| Matching a `Text` (longer) | 1.89 μs | 1.52 μs | 11.12 μs |+ If it's really regex processing that's causing a bottleneck, [pcre-light](https://hackage.haskell.org/package/pcre-light)/[-heavy](https://hackage.haskell.org/package/pcre-heavy)/[lens-regex-pcre](https://hackage.haskell.org/package/lens-regex-pcre)@@ -85,6 +87,7 @@ some upstream changes as well but in theory it's possible. * Improve compile time. * Support external `libpcre2` maybe.+* Support more types than just `Text`. ## License [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html).
bench/Bench.hs view
@@ -1,21 +1,20 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} module Main where -import Criterion.Main-import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.Text as Text-import Lens.Micro.Platform-import System.IO.Unsafe (unsafePerformIO)-import qualified Text.Regex.PCRE-import qualified Text.Regex.PCRE.Light.Char8-import qualified Text.Regex.PCRE.String-import qualified Text.Regex.PCRE.Text-import Text.Regex.Pcre2+import Data.List.NonEmpty (NonEmpty(..))+import Data.Text qualified as Text+import Lens.Micro.Platform+import System.IO.Unsafe (unsafePerformIO)+import Test.Tasty.Bench+import Text.Regex.PCRE qualified+import Text.Regex.PCRE.Light.Char8 qualified+import Text.Regex.PCRE.String qualified+import Text.Regex.PCRE.Text qualified+import Text.Regex.Pcre2 main :: IO () main = defaultMain [@@ -52,9 +51,9 @@ in bar], bgroup "texts" [- bgroupTexts "short" strings,+ bgroupTexts "shorter" strings, - bgroupTexts "long" (+ bgroupTexts "longer" ( "foo (ba*r) baz", "foo b" ++ replicate 1000 'a' ++ "r baz")],
pcre2.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.38.1.+-- This file has been generated from package.yaml by hpack version 0.39.1. -- -- see: https://github.com/sol/hpack name: pcre2-version: 2.3.0+version: 2.3.1 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@@ -203,7 +203,7 @@ , mtl , template-haskell , text >=2- default-language: Haskell2010+ default-language: GHC2021 if os(darwin) ghc-options: -optc=-Wno-ignored-qualifiers else@@ -231,7 +231,7 @@ , tasty-hunit , template-haskell , text >=2- default-language: Haskell2010+ default-language: GHC2021 benchmark pcre2-benchmarks type: exitcode-stdio-1.0@@ -240,17 +240,17 @@ Paths_pcre2 hs-source-dirs: bench- ghc-options: -threaded -rtsopts -with-rtsopts=-N+ ghc-options: -rtsopts build-depends: base >=4.9 && <5 , containers- , criterion , microlens , microlens-platform , mtl , pcre-light , pcre2 , regex-pcre-builtin+ , tasty-bench , template-haskell , text >=2- default-language: Haskell2010+ default-language: GHC2021
src/hs/Text/Regex/Pcre2.hs view
@@ -290,8 +290,8 @@ -- * User errors SomePcre2Exception(), Pcre2Exception(),- Pcre2CompileException())-where+ Pcre2CompileException(),+) where import Text.Regex.Pcre2.Internal import Text.Regex.Pcre2.TH
src/hs/Text/Regex/Pcre2/Internal.hs view
@@ -1,35 +1,32 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-}-{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} module Text.Regex.Pcre2.Internal where -import Control.Applicative (Alternative(..))-import Control.Exception-import Control.Monad-import Control.Monad.Cont-import Control.Monad.State.Strict-import Data.Either (partitionEithers)-import Data.Foldable (foldl', toList)-import Data.Functor.Identity (Identity(..))-import Data.IORef-import Data.List.NonEmpty (NonEmpty(..))-import Data.Maybe (fromMaybe)-import Data.Monoid (Alt(..), First)-import Data.Proxy (Proxy(..))-import Data.Text (Text)-import qualified Data.Text as Text-import qualified Data.Text.Foreign as Text-import Data.Typeable (cast)-import Foreign-import Foreign.C.Types (CInt(..), CUChar, CUInt(..))-import Lens.Micro-import Lens.Micro.Extras (preview, view)-import System.IO.Unsafe (unsafePerformIO)-import Text.Regex.Pcre2.Foreign+import Control.Applicative (Alternative(..))+import Control.Exception+import Control.Monad+import Control.Monad.Cont+import Control.Monad.State.Strict+import Data.Either (partitionEithers)+import Data.Foldable (foldl', toList)+import Data.Functor.Identity (Identity(..))+import Data.IORef+import Data.List.NonEmpty (NonEmpty(..))+import Data.Maybe (fromMaybe)+import Data.Monoid (Alt(..), First)+import Data.Proxy (Proxy(..))+import Data.Text (Text)+import Data.Text qualified as Text+import Data.Text.Foreign qualified as Text+import Data.Typeable (cast)+import Foreign+import Foreign.C.Types (CInt(..), CUChar, CUInt(..))+import Lens.Micro+import Lens.Micro.Extras (preview, view)+import System.IO.Unsafe (unsafePerformIO)+import Text.Regex.Pcre2.Foreign -- * General utilities @@ -1156,7 +1153,8 @@ -- FIXME Do not use Text.length since offsets are in code units | offset' == Text.length patt = pattLines | otherwise = insertCaretLine numberedPattLines- offset' = fromIntegral offset+ -- See https://github.com/PCRE2Project/pcre2/issues/906+ offset' = max (fromIntegral offset - 1) 0 pattLines = unchompedLines $ Text.unpack patt numberedPattLines = zip [0 :: Int ..] pattLines (caretRow, caretCol) = (!! offset') $ do
src/hs/Text/Regex/Pcre2/TH.hs view
@@ -1,43 +1,38 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} module Text.Regex.Pcre2.TH where -import Control.Applicative (Alternative(..))-import Control.Monad (forM)-import Control.Monad.State.Strict (evalStateT)-import Data.IORef-import Data.List (sortBy)-import Data.List.NonEmpty (NonEmpty)-import Data.Map.Lazy (Map)-import qualified Data.Map.Lazy as Map-import Data.Ord (comparing)-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 (If)-import Foreign-import Foreign.C (CUChar, CUInt)-import GHC.TypeLits hiding (Text)-import qualified GHC.TypeLits as TypeLits-import Language.Haskell.TH-import Language.Haskell.TH.Quote-import Language.Haskell.TH.Syntax (liftData)-import Lens.Micro-import Lens.Micro.Extras (view)-import System.IO.Unsafe (unsafePerformIO)-import Text.Regex.Pcre2.Foreign-import Text.Regex.Pcre2.Internal+import Control.Applicative (Alternative(..))+import Control.Monad (forM)+import Control.Monad.State.Strict (evalStateT)+import Data.IORef+import Data.List (sortBy)+import Data.List.NonEmpty (NonEmpty)+import Data.Map.Lazy (Map)+import Data.Map.Lazy qualified as Map+import Data.Ord (comparing)+import Data.Proxy (Proxy(..))+import Data.Text (Text)+import Data.Text qualified as Text+import Data.Text.Foreign qualified as Text+import Data.Type.Bool (If)+import Foreign+import Foreign.C (CUChar, CUInt)+import GHC.TypeLits hiding (Text)+import GHC.TypeLits qualified as TypeLits+import Language.Haskell.TH+import Language.Haskell.TH.Quote (QuasiQuoter(..))+import Language.Haskell.TH.Syntax (liftData)+import Lens.Micro+import Lens.Micro.Extras (view)+import System.IO.Unsafe (unsafePerformIO)+import Text.Regex.Pcre2.Foreign+import Text.Regex.Pcre2.Internal -- | A wrapper around a list of captures that carries additional type-level -- information about the number and names of those captures.@@ -59,7 +54,8 @@ -- | Look up the number of a capture at compile time, either by number or by -- name. Throw a helpful 'TypeError' if the index doesn't exist.-type family CaptNum (i :: k) (info :: CapturesInfo) :: Nat where+type CaptNum :: k -> CapturesInfo -> Nat+type family CaptNum i info where CaptNum (i :: Nat) '(hi, _) = If (i <=? hi) {- then -} i {- else -} (TypeError
src/hs/Text/Regex/Pcre2/Unsafe.hs view
@@ -28,8 +28,8 @@ CalloutIndex(..), CalloutResult(..), SubCalloutInfo(..),- SubCalloutResult(..))-where+ SubCalloutResult(..),+) where import Text.Regex.Pcre2.Internal import Text.Regex.Pcre2.TH
test/Spec.hs view
@@ -2,26 +2,24 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} module Main where -import Control.Applicative (Alternative)-import Control.Exception-import Control.Monad (forM_, void)-import Control.Monad.RWS.Lazy (RWS, ask, evalRWS, tell)-import Data.IORef (modifyIORef', newIORef, readIORef)-import Data.List.NonEmpty (NonEmpty(..))-import Data.Text (Text)-import qualified Data.Text as Text-import Lens.Micro.Platform-import Test.Tasty (defaultMain, testGroup)-import Test.Tasty.HUnit-import Text.Printf (printf)-import Text.Regex.Pcre2-import Text.Regex.Pcre2.Unsafe+import Control.Applicative (Alternative)+import Control.Exception+import Control.Monad (forM_, unless, void)+import Control.Monad.RWS.Lazy (RWS, ask, evalRWS, tell)+import Data.IORef (modifyIORef', newIORef, readIORef)+import Data.List.NonEmpty (NonEmpty(..))+import Data.Text (Text)+import Data.Text qualified as Text+import Lens.Micro.Platform+import Test.Tasty (defaultMain, testGroup)+import Test.Tasty.HUnit+import Text.Printf (printf)+import Text.Regex.Pcre2+import Text.Regex.Pcre2.Unsafe main :: IO () main = defaultMain $ testGroup "tests" [@@ -139,7 +137,20 @@ testCase "are catchable using instance Alternative IO" $ do let example = handle @SomePcre2Exception (\_ -> return "broken") $ broken "foo"- example >>= (@?= "broken")],+ example >>= (@?= "broken"),++ testCase ("show the caret in the right place" `issue` 52) $ do+ infoOrE <- try @Pcre2CompileException $+ predictCapturesInfo mempty "abc)def"+ msg <- case infoOrE of+ Right _ -> assertFailure "regex compiled"+ Left e -> return $ displayException e+ case lines msg of+ [_, pattLine, caretLine] ->+ assertBool ("caret doesn't line up:\n" ++ msg) $+ (')', '^') `elem` zip pattLine caretLine+ _ -> assertFailure $+ "didn't get 3-line message, got:\n" ++ msg], testGroup "native substitution" [ testCase "works using sub" $ do