packages feed

cryptostore-0.6.0.0: src/Crypto/Store/Utf8.hs

-- |
-- Module      : Crypto.Store.Utf8
-- License     : BSD-style
-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>
-- Stability   : experimental
-- Portability : unknown
--
-- UTF-8 encoding and decoding.  This implementation preserves surrogates and
-- does not use the replacement character.
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
module Crypto.Store.Utf8
    ( stringFromUTF8, stringToUTF8
    ) where

import           Data.Bits
import           Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Unsafe as B
import           Data.Char (chr, ord)
import           Data.Word

-- | Convert a string to UTF-8 encoding.
stringToUTF8 :: String -> ByteString
stringToUTF8 = B.pack . foldr charToUtf8 []

charToUtf8 :: Char -> [Word8] -> [Word8]
charToUtf8 c list
    | x < 0x80     = encode1
    | x < 0x800    = encode2
    | x < 0x10000  = encode3
    | x < 0x110000 = encode4
    | otherwise    = error ("charToUtf8: invalid code point: " ++ show x)
  where
    !x = fromIntegral (ord c) :: Word

    encode1 = fromIntegral x : list
    encode2 =
        let !x1 = fromIntegral (unsafeShiftR x 6 .|. 0xc0)
            !x2 = toC x
         in x1 : x2 : list
    encode3 =
        let !x1 = fromIntegral (unsafeShiftR x 12 .|. 0xe0)
            !x2 = toC (unsafeShiftR x 6)
            !x3 = toC x
         in x1 : x2 : x3 : list
    encode4 =
        let !x1 = fromIntegral (unsafeShiftR x 18 .|. 0xf0)
            !x2 = toC (unsafeShiftR x 12)
            !x3 = toC (unsafeShiftR x 6)
            !x4 = toC x
         in x1 : x2 : x3 : x4 : list

    toC :: Word -> Word8
    toC w = fromIntegral ((w .&. 0x3f) .|. 0x80)

-- | Convert a string from UTF-8 encoding.  When not fully valid, also return
-- the bytes that have not been converted.
stringFromUTF8 :: ByteString -> (String, ByteString)
stringFromUTF8 bs = go id 0
  where
    len = B.length bs

    go :: (String -> String) -> Int -> (String, ByteString)
    go f i
        | remaining < 1 = (f "", B.empty)
        | x1 < 0x80 =
            let w = fromIntegral x1
             in next w f (i + 1)
        | remaining < 2 || invalid x2 = end f i
        | x1 >= 0xc0 && x1 < 0xe0 =
            let w = unsafeShiftL (fromIntegral x1 .&. 0x1f) 6 .|.
                    (fromIntegral x2 .&. 0x3f)
             in next w f (i + 2)
        | remaining < 3 || invalid x3 = end f i
        | x1 >= 0xe0 && x1 < 0xf0 =
            let w = unsafeShiftL (fromIntegral x1 .&. 0x0f) 12 .|.
                    unsafeShiftL (fromIntegral x2 .&. 0x3f)  6 .|.
                    (fromIntegral x3 .&. 0x3f)
             in next w f (i + 3)
        | remaining < 4 || invalid x4 = end f i
        | x1 >= 0xf0 && x1 < 0xf8 =
            let w = unsafeShiftL (fromIntegral x1 .&. 0x07) 18 .|.
                    unsafeShiftL (fromIntegral x2 .&. 0x3f) 12 .|.
                    unsafeShiftL (fromIntegral x3 .&. 0x3f)  6 .|.
                    (fromIntegral x4 .&. 0x3f)
             in next w f (i + 4)
        | otherwise = end f i

      where
        remaining = len - i
        x1 = B.unsafeIndex bs i
        x2 = B.unsafeIndex bs (i + 1)
        x3 = B.unsafeIndex bs (i + 2)
        x4 = B.unsafeIndex bs (i + 3)

    end :: (String -> String) -> Int -> (String, ByteString)
    end f !i = (f "", B.drop i bs)

    next :: Word -> (String -> String) -> Int -> (String, ByteString)
    next w f i = let !c = chr (fromIntegral w) in go (f . (c :)) i

    invalid :: Word8 -> Bool
    invalid x = x < 0x80 || x >= 0xc0