Skip to content

Commit b91dcf2

Browse files
committed
Merge remote-tracking branch 'github/pr/199'
2 parents 61be653 + 094c9e1 commit b91dcf2

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

System/OsPath/Data/ByteString/Short/Internal.hs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# LANGUAGE UnboxedTuples #-}
55
{-# LANGUAGE BangPatterns #-}
66
{-# LANGUAGE ViewPatterns #-}
7+
{-# LANGUAGE UnliftedFFITypes #-}
78

89
-- |
910
-- Module : System.OsPath.Data.ByteString.Short.Internal
@@ -25,6 +26,13 @@ import Data.ByteString.Short.Internal (ShortByteString(..), length)
2526
#if !MIN_VERSION_base(4,11,0)
2627
import Data.Semigroup
2728
( Semigroup((<>)) )
29+
import Foreign.C.Types
30+
( CSize(..)
31+
, CInt(..)
32+
)
33+
import Data.ByteString.Internal
34+
( accursedUnutterablePerformIO
35+
)
2836
#endif
2937
#if !MIN_VERSION_bytestring(0,10,9)
3038
import Foreign.Marshal.Alloc (allocaBytes)
@@ -441,3 +449,29 @@ errorEmptySBS fun = moduleError fun "empty ShortByteString"
441449
moduleError :: HasCallStack => String -> String -> a
442450
moduleError fun msg = error (moduleErrorMsg fun msg)
443451
{-# NOINLINE moduleError #-}
452+
453+
compareByteArraysOff :: BA -- ^ array 1
454+
-> Int -- ^ offset for array 1
455+
-> BA -- ^ array 2
456+
-> Int -- ^ offset for array 2
457+
-> Int -- ^ length to compare
458+
-> Int -- ^ like memcmp
459+
#if MIN_VERSION_base(4,11,0)
460+
compareByteArraysOff (BA# ba1#) (I# ba1off#) (BA# ba2#) (I# ba2off#) (I# len#) =
461+
I# (compareByteArrays# ba1# ba1off# ba2# ba2off# len#)
462+
#else
463+
compareByteArraysOff (BA# ba1#) ba1off (BA# ba2#) ba2off len =
464+
assert (ba1off + len <= (I# (sizeofByteArray# ba1#)))
465+
$ assert (ba2off + len <= (I# (sizeofByteArray# ba2#)))
466+
$ fromIntegral $ accursedUnutterablePerformIO $
467+
c_memcmp_ByteArray ba1#
468+
ba1off
469+
ba2#
470+
ba2off
471+
(fromIntegral len)
472+
473+
474+
foreign import ccall unsafe "static sbs_memcmp_off"
475+
c_memcmp_ByteArray :: ByteArray# -> Int -> ByteArray# -> Int -> CSize -> IO CInt
476+
#endif
477+

System/OsPath/Data/ByteString/Short/Word16.hs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
{-# LANGUAGE CPP #-}
33
{-# LANGUAGE MultiWayIf #-}
44
{-# LANGUAGE RankNTypes #-}
5+
{-# LANGUAGE MagicHash #-}
56
{-# LANGUAGE ViewPatterns #-}
67
{-# LANGUAGE ScopedTypeVariables #-}
8+
{-# LANGUAGE TupleSections #-}
79

810
{-# OPTIONS_GHC -fno-warn-name-shadowing -fexpose-all-unfoldings #-}
911

@@ -143,10 +145,11 @@ module System.OsPath.Data.ByteString.Short.Word16 (
143145
useAsCWStringLen
144146
)
145147
where
146-
import System.OsPath.Data.ByteString.Short ( append, intercalate, concat, stripSuffix, stripPrefix, isInfixOf, isPrefixOf, isSuffixOf, breakSubstring, length, empty, null, ShortByteString(..), fromShort, toShort )
148+
import System.OsPath.Data.ByteString.Short ( append, intercalate, concat, stripSuffix, stripPrefix, isPrefixOf, isSuffixOf, length, empty, null, ShortByteString(..), fromShort, toShort )
147149
import System.OsPath.Data.ByteString.Short.Internal
148150
import Data.Bits
149-
( shiftR )
151+
( shiftR
152+
)
150153
import Data.Word
151154
import Prelude hiding
152155
( Foldable(..)
@@ -172,6 +175,7 @@ import Prelude hiding
172175
import qualified Data.Foldable as Foldable
173176
import GHC.ST ( ST )
174177
import GHC.Stack ( HasCallStack )
178+
import GHC.Exts ( inline )
175179

176180
import qualified Data.ByteString.Short.Internal as BS
177181
import qualified Data.List as List
@@ -647,6 +651,28 @@ splitWith p = \(assertEven -> sbs) -> if
647651
| otherwise -> a : go (tail b)
648652

649653

654+
-- | Check whether one string is a substring of another.
655+
isInfixOf :: ShortByteString -> ShortByteString -> Bool
656+
isInfixOf sbs = \s -> null sbs || not (null $ snd $ GHC.Exts.inline breakSubstring sbs s)
657+
658+
659+
-- algorithm: https://github.com/haskell/filepath/issues/195#issuecomment-1605633713
660+
breakSubstring :: ShortByteString -- ^ String to search for
661+
-> ShortByteString -- ^ String to search in
662+
-> (ShortByteString, ShortByteString) -- ^ Head and tail of string broken at substring
663+
breakSubstring bPat@(asBA -> pat) bInp@(asBA -> inp) = go 0
664+
where
665+
lpat = BS.length bPat
666+
linp = BS.length bInp
667+
go ix
668+
| let ix' = ix * 2
669+
, linp >= ix' + lpat =
670+
if | compareByteArraysOff pat 0 inp ix' lpat == 0 -> splitAt ix bInp
671+
| otherwise -> go (ix + 1)
672+
| otherwise
673+
= (bInp, mempty)
674+
675+
650676
-- ---------------------------------------------------------------------
651677
-- Reducing 'ByteString's
652678

tests/bytestring-tests/Properties/Common.hs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
module Properties.ShortByteString.Word16 (tests) where
2222
import System.OsPath.Data.ByteString.Short.Internal (_nul, isSpace)
2323
import qualified System.OsPath.Data.ByteString.Short.Word16 as B
24+
import qualified System.OsPath.Data.ByteString.Short as BS
2425
#else
2526
module Properties.ShortByteString (tests) where
2627
import qualified System.OsPath.Data.ByteString.Short as B
@@ -148,6 +149,28 @@ tests =
148149
, ("mempty []",
149150
once $ B.unpack mempty === [])
150151

152+
#ifdef WORD16
153+
, ("isInfixOf works correctly under UTF16",
154+
once $
155+
let foo = BS.pack [0xbb, 0x03]
156+
foo' = BS.pack [0xd2, 0xbb]
157+
bar = BS.pack [0xd2, 0xbb, 0x03, 0xad]
158+
bar' = BS.pack [0xd2, 0xbb, 0x03, 0xad, 0xd2, 0xbb, 0x03, 0xad, 0xbb, 0x03, 0x00, 0x00]
159+
in [B.isInfixOf foo bar, B.isInfixOf foo' bar, B.isInfixOf foo bar'] === [False, True, True]
160+
)
161+
#endif
162+
, ("break breakSubstring",
163+
property $ \(toElem -> c) x -> B.break (== c) x === B.breakSubstring (B.singleton c) x
164+
)
165+
, ("breakSubstring",
166+
property $ \x y -> not (B.null x) ==> B.null (snd (B.breakSubstring x y)) === not (B.isInfixOf x y)
167+
)
168+
, ("breakSubstring empty",
169+
property $ \x -> B.breakSubstring B.empty x === (B.empty, x)
170+
)
171+
, ("isInfixOf",
172+
property $ \x y -> B.isInfixOf x y === L.isInfixOf (B.unpack x) (B.unpack y))
173+
151174
, ("mconcat" ,
152175
property $ \xs -> B.unpack (mconcat xs) === mconcat (map B.unpack xs))
153176
, ("mconcat [x,x]" ,

0 commit comments

Comments
 (0)