strings-1.0.2: src/Data/Strings.hs
{-# LANGUAGE TypeSynonymInstances,
FlexibleInstances,
Trustworthy #-}
-- |
-- Copyright: Julian Fleischer
-- License: MIT
--
-- Maintainer: Julian Fleischer <julian.fleischer@fu-berlin.de>
-- Stability: provisional
-- Portability: portable
--
-- Functions for working with strings, including 'Text', 'ByteString', etc.
--
-- This module aims at offering a consistent interface across all the available
-- string types. It currently offers instances for the ordinary Haskell 'String'
-- type, 'Text', lazy 'LazyT.Text', 'ByteString', and lazy 'LazyB.ByteString'.
--
-- It especially provides functions for some types, which are otherwise not
-- available nativly (such as 'breakOnSubstring' which is not available for the
-- lazy 'LazyT.Text' type, is offered by 'sBreak' and 'strBreak').
module Data.Strings (
text,
lazyText,
bytes,
lazyBytes,
Str (..),
Strings (..)
) where
import Data.Int
import Data.Word
import Data.String hiding (fromString)
import qualified Data.String as S
import qualified Data.Char as C
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString as B
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy.Char8 as LazyB8
import qualified Data.ByteString.Lazy as LazyB
import qualified Data.Text as T
import Data.Text (Text)
import qualified Data.Text.Lazy as LazyT
import qualified Data.List as L
import qualified Data.Text.Encoding as Enc
import qualified Data.Text.Lazy.Encoding as LazyEnc
-- | Returns @True@ exactly for space (0x20), and horizontal tab (0x09).
isSpace x
| x == ' ' = True
| x == '\t' = True
| otherwise = False
-- | Returns @True@ exactly for space (0x20), horizontal tab (0x09), carriage return (0x0D), and line feed (0x0A).
isWhiteSpace x
| x == ' ' = True
| x == '\t' = True
| x == '\r' = True
| x == '\n' = True
| otherwise = False
-- | Create a 'Text' object form an ordinary Haskell 'String'.
text :: String -> Text
text = S.fromString
-- | Create a lazy 'LazyT.Text' object from an ordinary Haskell 'String'.
lazyText :: String -> LazyT.Text
lazyText = S.fromString
-- | Create a 'ByteString' object form an ordinary Haskell 'String'.
-- This function will encode a String using the UTF-8 character encoding.
bytes :: String -> ByteString
bytes = sFromUnicodeString
-- | Create a lazy 'LazyB.ByteString' object from an ordinary Haskell 'String'.
-- This function will encode a String using the UTF-8 character encoding.
lazyBytes :: String -> LazyB.ByteString
lazyBytes = sFromUnicodeString
-- | The 'Str' class provides functions for working with arbitrary Strings.
-- It is basically the same interface as provided by the 'Strings' class.
-- However, every input string is a Haskell String here, thus easing the
-- usage of different string types with native Haskell String literals.
--
-- For example @strAppend "suffix"@ works with any string type for which
-- an instance of @Str@ is defined. In order to maximize the ease of use
-- of this library, the functions are prefixed with @str@.
--
-- The complexity and efficiency of these functions depends on the underlying
-- string type being used.
--
-- Minimal complete definition: It suffices to provide instances for
-- 'Eq' and 'Strings'.
class (Eq a, Strings a) => Str a where
-- | Check whether the given string is empty or not. 'null' generalised.
strNull :: a -> Bool
-- | 'length' generalised.
strLen :: a -> Int
-- | 'head' generalised. This function is undefined if 'strNull' would return @True@.
strHead :: a -> Char
-- | 'last' generalised. This function is undefined if 'strNull' would return @True@.
strLast :: a -> Char
-- | 'init' generalised. This function is undefined if 'strNull' would return @True@.
strInit :: a -> a
-- | 'tail' generalised. This function is undefined if 'strNull' would return @True@.
strTail :: a -> a
-- | 'take' generalised.
strTake :: Int -> a -> a
-- | 'drop' generalised.
strDrop :: Int -> a -> a
-- | Replace a substring with another string.
strReplace :: String -- ^ Needle.
-> String -- ^ Replacement.
-> a -- ^ Haystack.
-> a -- ^ Result: @Haystack@ with @Needle@ replaced by @Replacement@.
-- | Breaks the string on the first occurence of the given substring.
--
-- > strBreak "xx" "1x2xx3xx4" = ("1x2", "xx3xx4")
strBreak :: String -> a -> (a, a)
-- | Like 'strBreak', but the string to break on is excluded from the result.
--
-- > strSplit "xx" "1x2xx3xx4" = ("1x2", "3xx4")
strSplit :: String -> a -> (a, a)
-- | Split a string into multiple fragments, separated by the given substring.
--
-- > strSplitAll "xx" "1x2xx3xx4" = ["1x2", "3", "4"]
strSplitAll :: String -> a -> [a]
-- | @concat@ generalised.
strConcat :: [a] -> a
-- | Glue together multiple strings by a given Haskell 'String'.
--
-- > strJoin x = concat . intersperse x
strJoin :: String -> [a] -> a
-- | Appends the given Haskell 'String' to the string. @++@ generalised.
--
-- > strAppend " world" "hello" = "hello world"
strAppend :: String -- The String to append.
-> a -- The string to append to.
-> a -- The concatenation of the two strings.
-- | Strips white space characters off both ends of the string.
strTrim :: a -> a
-- | Appends the given character n times to the left, such that
-- the resulting string has the given length.
--
-- > strPadLeft '0' 8 "4711" == "00004711"
strPadLeft :: Char -> Int -> a -> a
-- | Appends the given character n times to the right, such that
-- the resulting string has the given length.
--
-- > strPadRight '0' 8 "4711" == "47110000"
strPadRight :: Char -> Int -> a -> a
-- | Appends the given character n times to both sides, such that
-- the resulting string has the given length.
--
-- > strPadBoth '0' 8 "4711" == "00471100"
strPadBoth :: Char -> Int -> a -> a
-- | Reverse the string.
strReverse :: a -> a
-- | Check if the given Haskell String equals the string.
strEq :: String -- ^ The Haskell String.
-> a -- ^ The string.
-> Bool -- @True@ iff @fromString haskellString == string@
-- | Check if the string starts with the given Haskell String.
strStartsWith :: a -> String -> Bool
-- | Check if the string ends with the given Haskell String.
strEndsWith :: a -> String -> Bool
-- | Create a string from a Haskell String.
fromString :: String -> a
-- | Create a string from a Haskell String. If the string does not support unicode,
-- the Haskell String is encoded using UTF-8.
fromUnicodeString :: String -> a
-- | Convert the string into a Haskell String.
toString :: a -> String
-- | Convert the string into a list of bytes.
toWord8 :: a -> [Word8]
strNull = sNull
strLen = sLen
strHead = sHead
strLast = sLast
strInit = sInit
strTail = sTail
strTake = sTake
strDrop = sDrop
strReplace n r = sReplace (fromString n) (fromString r)
strBreak n = sBreak (fromString n)
strSplit d s = (a, b)
where (a, b, _) = sSplit (fromString d) s
strSplitAll n = sSplitAll (fromString n)
strConcat = sConcat
strJoin d = let d' = fromUnicodeString d in strConcat . L.intersperse d'
strAppend x xs = sConcat [xs, fromString x]
strTrim = sTrim
strPadLeft = sPadLeft
strPadRight = sPadRight
strPadBoth = sPadBoth
strReverse = sReverse
strStartsWith s pref = sStartsWith s (fromString pref)
strEndsWith s suff = sEndsWith s (fromString suff)
strEq s1 s2 = fromUnicodeString s1 == s2
fromString = sFromString
fromUnicodeString = sFromUnicodeString
toString = sToString
toWord8 = sToWord8
{-
instance IsString Data.ByteString.ByteString where
fromString = Enc.encodeUtf8 . Data.Text.pack
instance IsString Data.ByteString.Lazy.ByteString where
fromString = LazyEnc.encodeUtf8 . Data.Text.Lazy.pack
-}
instance Str Text where
instance Str LazyT.Text where
instance Str ByteString where
instance Str LazyB.ByteString where
instance Str String where
-- | The goal of this class is to offer the same interface for various
-- types of strings ('ByteString', 'Text', Haskell 'String', etc.).
-- If a certain type offers a native implementation for a given function,
-- 'Strings' uses it. If not, a default implementation is given.
--
-- All of these functions are prefixed with @s@ to avoid nameclashes
-- with existing functions from the prelude.
--
-- The complexity and efficiency of these functions depends on the underlying
-- string type being used.
class Strings a where
-- | Check whether the given string is empty or not. @null@ generalised.
sNull :: a -> Bool
-- | The empty string.
sEmpty :: a
-- | 'length' generalised.
sLen :: a -> Int
-- | 'head' generalised. This function is undefined if 'strNull' would return @True@.
sHead :: a -> Char
-- | 'last' generalised. This function is undefined if 'strNull' would return @True@.
sLast :: a -> Char
-- | 'init' generalised. This function is undefined if 'strNull' would return @True@.
sInit :: a -> a
-- | 'tail' generalised. This function is undefined if 'strNull' would return @True@.
sTail :: a -> a
-- | 'take' generalised.
sTake :: Int -> a -> a
-- | 'drop' generalised.
sDrop :: Int -> a -> a
-- | 'takeWhile' generalised.
sTakeWhile :: (Char -> Bool) -> a -> a
-- | 'dropWhile' generalised.
sDropWhile :: (Char -> Bool) -> a -> a
-- | Replace a substring with another string.
sReplace :: a -> a -> a -> a
-- | Breaks the string on the first occurence of the given substring.
--
-- > strBreak "xx" "1x2xx3xx4" = ("1x2", "xx3xx4")
sBreak :: a -> a -> (a, a)
-- | Like 'sBreak', but the string to break on is excluded from the result.
--
-- > strSplit "xx" "1x2xx3xx4" = ("1x2", "3xx4")
sSplit :: a -> a -> (a, a, Bool)
-- | Split a string into multiple fragments, separated by the given substring.
--
-- > strSplitAll "xx" "1x2xx3xx4" = ["1x2", "3", "4"]
sSplitAll :: a -> a -> [a]
-- | Check if the string starts with the given string.
sStartsWith :: a -> a -> Bool
-- | Check if the string ends with the given string.
sEndsWith :: a -> a -> Bool
-- | Concatenate all the strings in the list to a single string.
sConcat :: [a] -> a
-- | Strips white space characters off both ends of the string.
sTrim :: a -> a
-- | Appends the given character n times to the left, such that
-- the resulting string has the given length.
--
-- > strPadLeft '0' 8 "4711" == "00004711"
sPadLeft :: Char -> Int -> a -> a
-- | Appends the given character n times to the right, such that
-- the resulting string has the given length.
--
-- > strPadRight '0' 8 "4711" == "47110000"
sPadRight :: Char -> Int -> a -> a
-- | Appends the given character n times to both sides, such that
-- the resulting string has the given length.
--
-- > strPadBoth '0' 8 "4711" == "00471100"
sPadBoth :: Char -> Int -> a -> a
-- 'reverse' the string.
sReverse :: a -> a
-- | Create a string from a Haskell String.
sFromString :: String -> a
-- | Create a string from a Haskell String. If the string does not support unicode,
-- the Haskell String is encoded using UTF-8.
sFromUnicodeString :: String -> a
-- | Convert the string into a Haskell String.
sToString :: a -> String
-- | Convert the string into a list of bytes.
sToWord8 :: a -> [Word8]
sBreak d src = search 0 src
where
search a b | a `seq` b `seq` False = undefined
search n s
| sNull s = (src, sEmpty)
| sStartsWith s d = (sTake n src, s)
| otherwise = search (n+1) (sTail s)
sReplace n r = sConcat . replace n r
where
replace n r h = if sNull b
then (if c then [a, r] else [a])
else a : r : replace n r b
where (a, b, c) = sSplit n h
sSplit d s = (a, sDrop (sLen d) b, not $ sNull b)
where (a, b) = sBreak d s
sSplitAll d s = if sNull b
then (if c then [a, b] else [a])
else a : sSplitAll d b
where (a, b, c) = sSplit d s
sPadLeft c n s = let len = sLen s
padLen = max 0 (n - len)
padStr = sFromString $ replicate padLen c
in sConcat [padStr, s]
sPadRight c n s = let len = sLen s
padLen = max 0 (n - len)
padStr = sFromString $ replicate padLen c
in sConcat [s, padStr]
sPadBoth c n s = let len = sLen s
padLen = (max 0 (n - len))
padLenR = padLen `quot` 2
padLenL = padLen - padLenR
padStrL = sFromString $ replicate padLenL c
padStrR = sFromString $ replicate padLenR c
in sConcat [padStrL, s, padStrR]
sFromString = sFromUnicodeString
sToWord8 = sToWord8 . sToString
instance Strings ByteString where
sNull = B.null
sEmpty = B.empty
sHead = toEnum . fromIntegral . B.head
sLast = toEnum . fromIntegral . B.last
sInit = B.init
sTail = B.tail
sTake = B.take
sTakeWhile f = B.takeWhile (f . chr)
sDrop = B.drop
sDropWhile f = B.dropWhile (f . chr)
sBreak = B.breakSubstring
sStartsWith = flip B.isPrefixOf
sEndsWith = flip B.isSuffixOf
sConcat = B.concat
sLen = B.length
sTrim s = let (a, b) = B.span (isWhiteSpace . chr) s
(c, d) = B.spanEnd (isWhiteSpace . chr) b
in c
sReverse = B.reverse
sFromString = B8.pack
sFromUnicodeString = Enc.encodeUtf8 . T.pack
sToString = B8.unpack
sToWord8 = B.unpack
instance Strings LazyB.ByteString where
sNull = LazyB.null
sEmpty = LazyB.empty
sHead = toEnum . fromIntegral . LazyB.head
sLast = toEnum . fromIntegral . LazyB.last
sInit = LazyB.init
sTail = LazyB.tail
sTake = LazyB.take . fromIntegral
sTakeWhile f = LazyB.takeWhile (f . chr)
sDrop = LazyB.drop . fromIntegral
sDropWhile f = LazyB.dropWhile (f . chr)
sStartsWith = flip LazyB.isPrefixOf
sEndsWith = flip LazyB.isSuffixOf
sConcat = LazyB.concat
sLen = fromIntegral . LazyB.length
sTrim = LazyB.fromChunks . (:[]) . sTrim . B.concat . LazyB.toChunks
sReverse = LazyB.reverse
sFromString = LazyB8.pack
sFromUnicodeString = LazyEnc.encodeUtf8 . LazyT.pack
sToString = LazyB8.unpack
sToWord8 = LazyB.unpack
instance Strings Text where
sNull = T.null
sEmpty = T.empty
sHead = T.head
sLast = T.last
sInit = T.init
sTail = T.tail
sTake = T.take
sTakeWhile = T.takeWhile
sDrop = T.drop
sDropWhile = T.dropWhile
sReplace = T.replace
sBreak = T.breakOn
sStartsWith = flip T.isPrefixOf
sEndsWith = flip T.isSuffixOf
sConcat = T.concat
sLen = T.length
sTrim = T.strip
sPadLeft = flip T.justifyLeft
sPadRight = flip T.justifyRight
sPadBoth = flip T.center
sReverse = T.reverse
sFromUnicodeString = S.fromString
sFromString = S.fromString
sToString = T.unpack
sToWord8 = B.unpack . Enc.encodeUtf8
instance Strings LazyT.Text where
sNull = LazyT.null
sEmpty = LazyT.empty
sHead = LazyT.head
sLast = LazyT.last
sInit = LazyT.init
sTail = LazyT.tail
sTake = LazyT.take . fromIntegral
sTakeWhile = LazyT.takeWhile
sDrop = LazyT.drop . fromIntegral
sDropWhile = LazyT.dropWhile
sBreak = LazyT.breakOn
sStartsWith = flip LazyT.isPrefixOf
sEndsWith = flip LazyT.isSuffixOf
sConcat = LazyT.concat
sLen = fromIntegral . LazyT.length
sTrim = LazyT.strip
sPadLeft = flip (LazyT.justifyLeft . fromIntegral)
sPadRight = flip (LazyT.justifyRight . fromIntegral)
sPadBoth = flip (LazyT.center . fromIntegral)
sReverse = LazyT.reverse
sFromUnicodeString = S.fromString
sFromString = S.fromString
sToString = LazyT.unpack
sToWord8 = LazyB.unpack . LazyEnc.encodeUtf8
instance Strings String where
sNull = null
sEmpty = []
sHead = head
sLast = last
sInit = init
sTail = tail
sTake = take
sTakeWhile = takeWhile
sDrop = drop
sDropWhile = dropWhile
sStartsWith = flip L.isPrefixOf
sEndsWith = flip L.isSuffixOf
sConcat = concat
sLen = length
sTrim = T.unpack . T.strip . T.pack
sReverse = reverse
sFromString = id
sFromUnicodeString = id
sToString = id
sToWord8 = B.unpack . Enc.encodeUtf8 . T.pack
-- | Utility class for working with characters.
class Chars a where
-- | Retrieve the value of the given character.
ord :: Char -> a
-- | Construct a character form the given value.
chr :: a -> Char
instance Chars Int where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Word8 where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Word16 where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Word32 where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Word64 where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Int8 where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Int16 where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Int32 where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Int64 where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral
instance Chars Integer where
ord = fromIntegral . fromEnum
chr = toEnum . fromIntegral