packages feed

DPutils 0.0.0.2 → 0.0.1.0

raw patch · 6 files changed

+307/−22 lines, 6 filesdep +bytestringdep +lensdep +mtldep ~kan-extensionsdep ~paralleldep ~pipesPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, lens, mtl, pipes-bytestring, pipes-parse, quickcheck-instances, stringsearch, transformers

Dependency ranges changed: kan-extensions, parallel, pipes, vector

API changes (from Hackage documentation)

+ Data.Char.Util: c2w8 :: Char -> Word8
+ Data.Char.Util: w82c :: Word8 -> Char
+ Data.Vector.Generic.Unstream: streamToVectorM :: forall m v a. (Monad m, Vector v a) => Stream m a -> m (v a)
+ Pipes.Split.ByteString: referenceByteStringTokenizer :: ByteString -> ByteString -> [ByteString]
+ Pipes.Split.ByteString: splitGeneric :: Monad m => (ByteString -> Int -> Int -> Int -> (ByteString, ByteString)) -> ByteString -> Lens' (Producer ByteString m x) (Producer ByteString m (Producer ByteString m x))
+ Pipes.Split.ByteString: splitKeepEnd :: Monad m => ByteString -> Lens' (Producer ByteString m x) (Producer ByteString m (Producer ByteString m x))
+ Pipes.Split.ByteString: splitKeepStart :: Monad m => ByteString -> Lens' (Producer ByteString m x) (Producer ByteString m (Producer ByteString m x))
+ Pipes.Split.ByteString: type Lens' a b = forall f. Functor f => (b -> f b) -> (a -> f a)

Files

DPutils.cabal view
@@ -1,5 +1,5 @@ Name:           DPutils-Version:        0.0.0.2+Version:        0.0.1.0 License:        BSD3 License-file:   LICENSE Maintainer:     choener@bioinf.uni-leipzig.de@@ -27,21 +27,29 @@  Library   Exposed-modules:+    Data.Char.Util     Data.Paired.Common     Data.Paired.Foldable     Data.Paired.Vector+    Data.Vector.Generic.Unstream     Math.TriangularNumbers     Pipes.Parallel+    Pipes.Split.ByteString   build-depends: base             >= 4.7    &&  < 5.0+               , bytestring                , containers-               , kan-extensions   >= 4.0    &&  < 6.0-               , parallel         >= 3.0    &&  < 4.0-               , pipes            >= 4.0    &&  < 4.3+               , kan-extensions   >= 4.0+               , parallel         >= 3.0+               , pipes            >= 4.0                , QuickCheck       >= 2.7-               , vector           >= 0.10   &&  < 0.12+               , stringsearch     >= 0.3+               , transformers     >= 0.5+               , vector           >= 0.10   default-extensions: BangPatterns                     , CPP+                    , RankNTypes                     , ScopedTypeVariables+                    , TypeFamilies                     , FlexibleContexts   default-language:     Haskell2010@@ -63,18 +71,27 @@   default-language:     Haskell2010   default-extensions: CPP+                    , RankNTypes+                    , ScopedTypeVariables                     , TemplateHaskell   build-depends: base+               , bytestring                , containers-               , DPutils                , kan-extensions+               , lens                 >= 4.0+               , mtl                , parallel                , pipes+               , pipes-bytestring     >= 2.0+               , pipes-parse          >= 3.0                , QuickCheck-               , tasty              >= 0.11-               , tasty-quickcheck   >= 0.8-               , tasty-th           >= 0.1+               , quickcheck-instances >= 0.3+               , tasty                >= 0.11+               , tasty-quickcheck     >= 0.8+               , tasty-th             >= 0.1                , vector+               --+               , DPutils   @@ -83,8 +100,9 @@     exitcode-stdio-1.0   build-depends: base                , criterion  >= 1.1-               , DPutils                , vector+               --+               , DPutils   hs-source-dirs:     tests   main-is:
+ Data/Char/Util.hs view
@@ -0,0 +1,16 @@++-- | Convert between @Word8@ and @Char@. Mostly for attoparsec's bytestring+-- module.++module Data.Char.Util where++import Data.Word (Word8)++c2w8 :: Char -> Word8+c2w8 = fromIntegral . fromEnum+{-# Inline c2w8 #-}++w82c :: Word8 -> Char+w82c = toEnum . fromIntegral+{-# Inline w82c #-}+
+ Data/Vector/Generic/Unstream.hs view
@@ -0,0 +1,36 @@++-- | Helper functions for turnings streams into vectors.+--+-- Mostly very similar to bundle conversion functions from the @vector@+-- package.++module Data.Vector.Generic.Unstream where++import           Control.Monad.ST+import           GHC.Conc (pseq)+import qualified Data.Vector.Fusion.Stream.Monadic as SM+import qualified Data.Vector.Generic as VG+import qualified Data.Vector.Generic.Mutable as VGM+import           System.IO.Unsafe (unsafePerformIO)++-- for testing++import qualified Data.Vector.Unboxed as VU++++-- | Turns a stream into a vector.+--+-- TODO insert index checks? Generalized @flag devel@++streamToVectorM :: forall m v a . (Monad m, VG.Vector v a) => SM.Stream m a -> m (v a)+streamToVectorM s = do+  let mv' = unsafePerformIO $ VGM.unsafeNew 1+  let put (v',i) x =+        do let v = unsafePerformIO $ if (i < VGM.length v') then return v' else VGM.unsafeGrow v' (max 1 $ VGM.length v')+           seq (unsafePerformIO $ VGM.unsafeWrite v i x) (return (v,i+1))+      {-# Inline [0] put #-}+  (mv,written) <- SM.foldlM' put (mv',0) s+  mv `pseq` return . unsafePerformIO . VG.freeze $ VGM.unsafeSlice 0 written mv+{-# Inline streamToVectorM #-}+
+ Pipes/Split/ByteString.hs view
@@ -0,0 +1,140 @@++-- | Split incombing bytestrings based on bytestrings.++module Pipes.Split.ByteString where++import           Control.Monad (join,unless)+import           Control.Monad.Trans.Class (lift)+import           Data.ByteString (ByteString)+import           Data.ByteString.Search (indices)+import           Data.Monoid ((<>))+import           Debug.Trace+import           Pipes (Producer,next,yield)+import qualified Data.ByteString as BS++++type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)++-- | Splits bytestrings after each pattern @pat@. Tries to minimize the+-- number of intermediate bytestring constructors.+--+-- The following function @ske@ expects a string @str@ and a pattern @pat@+-- and then returns a tuple with the splitted bytestrings in @fst@ and the+-- return value in @snd@.+--+-- The inner parser @parse@ uses @zoom@ to draw the full inner producer,+-- which should contain just one bytestring, namely one of the split off+-- ones. @parse@ doesn't do anything with the inner producer, except+-- returning the contained bytestring.+--+-- @parse@ returns @Right $ concat xs@ on a correct parse, and @Left []@+-- once the input has been exhausted.+--+-- @+-- ske :: ByteString -> ByteString -> ([ByteString],[ByteString],[ByteString])+-- ske pat str | BS.null pat || BS.null str = ([],[],[])+-- ske pat str =+--   let parse = do+--         xs <- zoom (splitKeepEnd pat) PP.drawAll+--         case xs of+--           [] -> return $ Left []+--           xs -> return $ Right $ BS.concat xs+--       (a,(b,p)) = runIdentity . P.toListM' $ PP.parsed parse $ PP.yield str+--   in (a,b, fst . runIdentity . P.toListM' $ p)+-- @++splitKeepEnd :: Monad m => ByteString -> Lens' (Producer ByteString m x) (Producer ByteString m (Producer ByteString m x))+splitKeepEnd pat k p0 = fmap join (k (go BS.empty p0)) where+  go pre p = do+    x <- lift (next p)+    case x of+      Left r -> return $ return r+      Right (bs, p') -> do+        case fnd (pre <> bs) of+          -- no hit yet, send the bs down, keep some suffix+          [] -> do+            unless (BS.null bs) (yield bs)+            let pfx = BS.drop (BS.length bs - l + 1) bs+            go pfx p'+          -- at least one hit, split off the correct part, remainder goes+          -- back.+          (k:_) -> do+            let (y,suf) = BS.splitAt (k - BS.length pre + l) bs+            yield y+            return (yield suf >> p')+  l = BS.length pat+  fnd = indices pat+{-# Inlineable splitKeepEnd #-}++++-- | Split a string into substrings, where each substring starts with @pat@+-- and continues until just before the next @pat@ (or until there is no+-- more input).+--+-- Any prefix that does not start with the substring is /kept/!+--+-- Since each substring is supposed to start with @pat@, there is a small+-- problem. What about a header that prefixes the string we are interested+-- in?++splitKeepStart :: Monad m => ByteString -> Lens' (Producer ByteString m x) (Producer ByteString m (Producer ByteString m x))+splitKeepStart = splitGeneric (\bs k p l -> BS.splitAt (k - p) bs)+{-# Inlineable splitKeepStart #-}++++-- | Generic splitting function. Takes a bytestring @[a,b,c]@ (where+-- @a,b,c@ are substrings of the bytestring!) and performs the split.+--++splitGeneric+  :: Monad m+  => (ByteString -> Int -> Int -> Int -> (ByteString,ByteString))+  -- ^ splitter function+  -> ByteString+  -- ^ pattern to split on+  -> Lens' (Producer ByteString m x) (Producer ByteString m (Producer ByteString m x))+  -- ^ lens into the individual split off bytestrings+splitGeneric splt pat k p0 = fmap join (k (go BS.empty p0)) where+  go pre p = do+    x <- lift (next p)+    case x of+      Left r -> do+        -- yield final split off string+        unless (BS.null pre) (yield pre)+        return $ return r+      Right (bs, p') -> do+        -- will not search in the part of the prefix that *can not contain*+        -- the @pat@tern.+        case fnd ((BS.drop (BS.length pre - l) pre) <> bs) of+          -- no hit yet, send the prefix down completely, make bs new+          -- prefix if possible. If either @pre@ or @bs@ are too short, we+          -- keep @pre <> bs@ for the next round. This should not happen if+          -- the pattern is reasonably short compared to the size of the+          -- bytestring chunks.+          [] -> do+            if (BS.length bs >= l)+            then yield pre >> go bs p'+            else go (pre <> bs) p'+          -- at least one hit, split off the correct part, remainder goes+          -- back.+          (k:_) -> do+            let (y,suf) = splt bs k (BS.length pre) l+            yield y+            return (yield suf >> p')+  l = BS.length pat+  fnd = indices pat+{-# Inline splitGeneric #-}++++-- manual splitting, for @splitKeepEnd@++referenceByteStringTokenizer pat str | BS.null pat || BS.null str = []+referenceByteStringTokenizer pat str+  = (h `BS.append` BS.take (BS.length pat) t)+  : if BS.null t then [] else referenceByteStringTokenizer pat (BS.drop (BS.length pat) t)+    where (h,t) = BS.breakSubstring pat str+
changelog.md view
@@ -1,3 +1,9 @@+0.0.1.0+-------++- Pipes.Split.ByteString splits a bytestring based on a given pattern (example+  usage in tests/properties.hs)+ 0.0.0.2 ------- 
tests/properties.hs view
@@ -1,19 +1,30 @@  module Main where -import Data.List as L-import Data.Map.Strict as M-import Data.Tuple (swap)-import Data.Vector as V-import Debug.Trace-import Test.QuickCheck-import Test.Tasty-import Test.Tasty.QuickCheck as QC-import Test.Tasty.TH+import           Control.Lens+import           Control.Monad.Identity+import           Data.ByteString (ByteString)+import           Data.List as L+import           Data.Map.Strict as M+import           Data.Tuple (swap)+import           Data.Vector as V+import           Debug.Trace+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy as BSL+import qualified Pipes as P+import qualified Pipes.ByteString as PB+import qualified Pipes.Parse as PP+import qualified Pipes.Prelude as P+import           Test.QuickCheck+import           Test.QuickCheck.Instances+import           Test.Tasty+import           Test.Tasty.QuickCheck as QC+import           Test.Tasty.TH -import Data.Paired.Vector as DPV-import Data.Paired.Foldable as DPF-import Math.TriangularNumbers+import           Data.Paired.Foldable as DPF+import           Data.Paired.Vector as DPV+import           Math.TriangularNumbers+import           Pipes.Split.ByteString   @@ -138,6 +149,64 @@              | (k,(i,j)) <- ls ]  --++-- | Check if both splitKeepEnd and simple tokenization provide the same+-- result.++prop_splitKeepEndStrict :: String -> Small Int -> Small Int -> Bool+prop_splitKeepEndStrict str' (Small k) (Small l)+  | tt == ss  = True+  | otherwise = traceShow ("ske",pat,str,k,l,tt,ss,ee) False+  where str = BS.concat . L.replicate skeMult $ BS.pack str'+        -- make a small pattern with a chance that it repeats+        pat = BS.take (l `mod` 2 + 1) $ BS.drop (k `mod` 10) str+        -- what ske thinks is a good split+        (ss,ee,_) = ske pat str+        -- manual splitting+        tt = referenceByteStringTokenizer pat str++-- | Check if both splitKeepEnd and simple tokenization provide the same+-- result.++prop_splitKeepEndLazy :: String -> Small Int -> Small Int -> Bool+prop_splitKeepEndLazy str' (Small k) (Small l)+  | tt == ll  = True+  | otherwise = traceShow ("ske'",pat,str',str,strL,k,l,tt,ll,ee,rr) False+  where str = BS.concat . L.replicate skeMult $ BS.pack str'+        strL = BSL.fromChunks $ L.replicate skeMult $ BS.pack str'+        -- make a small pattern with a chance that it repeats+        pat = BS.take (l `mod` 2 + 1) $ BS.drop (k `mod` 10) str+        -- what we get with the lazy version+        (ll,ee,rr) = ske' pat strL+        -- manual splitting+        tt = referenceByteStringTokenizer pat str++-- The actual splitting system++ske :: ByteString -> ByteString -> ([ByteString],[ByteString],[ByteString])+ske pat str | BS.null pat || BS.null str = ([],[],[])+ske pat str =+  let parse = do+        xs <- zoom (splitKeepEnd pat) PP.drawAll+        case xs of+          [] -> return $ Left []+          xs -> return $ Right $ BS.concat xs+      (a,(b,p)) = runIdentity . P.toListM' $ PP.parsed parse $ PP.yield str+  in (a,b, fst . runIdentity . P.toListM' $ p)++ske' :: ByteString -> BSL.ByteString -> ([ByteString],[ByteString],[ByteString])+ske' pat _ | BS.null pat = ([],[],[])+ske' pat str =+  let parse = do+        xs <- zoom (splitKeepEnd pat) PP.drawAll+        case xs of+          [] -> return $ Left []+          xs -> return $ Right $ BS.concat xs+      (a,(b,p)) = runIdentity . P.toListM' $ PP.parsed parse $ PB.fromLazy str+  in (a,b, fst . runIdentity . P.toListM' $ p)++skeMult :: Int+skeMult = 1000  main :: IO () main = $(defaultMainGenerator)