packages feed

from-text-0.1: src/Data/Text/From.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnboxedTuples #-}

-- | Convert strict 'T.Text' to other textual types,
-- including t'ByteArray', 'B.ByteString' and 'System.OsPath.OsPath'.
module Data.Text.From (
  IsText (..),
) where

import Data.Array.Byte (ByteArray (..))
import Data.Bits (shiftR, (.&.))
import qualified Data.ByteString as B
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Short as BS
import Data.Char (ord)
import Data.Coerce (coerce)
import Data.Functor.Const (Const (..))
import Data.Functor.Identity (Identity (..))
import qualified Data.Text as T
import qualified Data.Text.Array as TA
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Internal as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TLB
import qualified Data.Text.Unsafe as TU
import GHC.Exts (sizeofByteArray#, writeWord16Array#)
import GHC.Int (Int (..))
import GHC.ST (ST (..), runST)
import GHC.Word (Word16 (..))
import qualified System.OsString as OS
import qualified System.OsString.Data.ByteString.Short.Internal as OSI
import qualified System.OsString.Internal.Types as OSIT
import qualified System.OsString.Posix as OSP

-- | Convert strict 'T.Text' to other textual types.
--
-- This is modeled after 'Data.String.IsString'
-- with the aim to avoid dealing with 'String' ever.
class IsText a where
  fromText :: T.Text -> a

instance a ~ Char => IsText [a] where
  fromText = T.unpack

instance IsText a => IsText (Identity a) where
  fromText = coerce (fromText @a)

instance IsText a => IsText (Const a b) where
  fromText = coerce (fromText @a)

-- | Encodes as UTF-8.
instance IsText ByteArray where
  fromText (T.Text arr@(ByteArray ba) 0 len)
    | I# (sizeofByteArray# ba) == len = arr
  fromText (T.Text arr off len) = TA.run $ do
    marr <- TA.new len
    TA.copyI len marr 0 arr off
    pure marr

-- | Encodes as UTF-8.
instance IsText B.StrictByteString where
  fromText = TE.encodeUtf8

-- | Encodes as UTF-8.
instance IsText BL.LazyByteString where
  fromText = BL.fromStrict . TE.encodeUtf8

-- | Encodes as UTF-8.
instance IsText BB.Builder where
  fromText = TE.encodeUtf8Builder

-- | Encodes as UTF-8.
instance IsText BS.ShortByteString where
  fromText = coerce (fromText @ByteArray)

instance IsText T.StrictText where
  fromText = id

instance IsText TL.LazyText where
  fromText = TL.fromStrict

instance IsText TLB.Builder where
  fromText = TLB.fromText

-- | Encodes as UTF-8.
instance IsText OSP.PosixString where
  fromText = coerce (fromText @ByteArray)

-- | Encodes as UTF-16 LE.
instance IsText OSIT.WindowsString where
  fromText (T.Text src off len) = runST $ do
    marr <- TA.new (len * 2)
    let go !srcOff !dstOff
          | srcOff >= len + off = do
              TA.shrinkM marr (dstOff * 2)
              arr <- TA.unsafeFreeze marr
              pure $ coerce arr
          | otherwise = do
              let !(TU.Iter c d) = TU.iterArray src srcOff
                  n = ord c
              d' <-
                if n <= 0xFFFF
                  then do
                    writeWord16LE marr dstOff (fromIntegral n)
                    pure 1
                  else do
                    let n1 = n - 0x10000
                    writeWord16LE marr dstOff (fromIntegral $ n1 `shiftR` 10 + 0xD800)
                    writeWord16LE marr (dstOff + 1) (fromIntegral $ n1 .&. 0x3FF + 0xDC00)
                    pure 2
              go (srcOff + d) (dstOff + d')
    go off 0
    where
      writeWord16LE :: TA.MArray s -> Int -> Word16 -> ST s ()
      writeWord16LE (TA.MutableByteArray marr) (I# offset) (W16# w16) = ST $ \s ->
        case writeWord16Array# marr offset (OSI.word16ToLE# w16) s of
          s' -> (# s', () #)

-- | Also known as 'System.OsPath.OsPath'.
instance IsText OS.OsString where
  fromText = case OS.coercionToPlatformTypes of
    Left {} -> coerce (fromText @OSIT.WindowsString)
    Right {} -> coerce (fromText @OSP.PosixString)