packed-0.1.0: src/Packed/Text.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UnboxedSums #-}
{-# LANGUAGE UnboxedTuples #-}
{-# OPTIONS_GHC
-Weverything
-fno-warn-unsafe
-fno-warn-implicit-prelude
-fno-warn-missing-import-lists
-O2
#-}
module Packed.Text
( Text(..)
, empty
, pack
, unpack
, breakChar
, map
, toUpper
, take
, drop
, dropEnd
, length
-- * Encoding
, decodeAscii
, decodeResumeAscii
, decodeUtf8
, decodeResumeUtf8
, encodeUtf8
) where
import Prelude hiding (map,take,drop,length)
import Data.Char (ord,chr)
import Data.Primitive (MutableByteArray)
import Packed.Bytes.Small (ByteArray)
import GHC.Exts (Word#)
import GHC.Int (Int(I#))
import GHC.Word (Word(W#),Word8(W8#))
import Data.Bits ((.&.),(.|.),unsafeShiftR,unsafeShiftL,complement)
import Control.Monad.ST (ST,runST)
import Packed.Bytes (Bytes(..))
import qualified Data.Char
import qualified Packed.Bytes.Small as BA
import qualified Packed.Bytes.Window as BAW
import qualified Packed.Bytes as B
import qualified Data.Primitive as PM
data Text = Text
{-# UNPACK #-} !ByteArray -- payload, normal UTF8-encoded text, nothing special like the unsliced variant
{-# UNPACK #-} !Word -- offset in bytes, not in characters, first bit reserved
{-# UNPACK #-} !Int -- length in bytes, not in characters
-- The byte-multiplicity for text is always Multiple if
-- a multi-byte character is present. If no multi-byte
-- characters are present, it may be set to either single
-- or multiple. Functions should try to set it to single
-- when possible.
instance Eq Text where
t1 == t2 = Bytes arr1 off1 len1 == Bytes arr2 off2 len2
where
!(!arr1,!off1,!len1,!_) = textMatch t1
!(!arr2,!off2,!len2,!_) = textMatch t2
instance Show Text where
show = show . unpack
newtype Multiplicity = Multiplicity Word
deriving Eq
appendMult :: Multiplicity -> Multiplicity -> Multiplicity
appendMult (Multiplicity a) (Multiplicity b) = Multiplicity (a .|. b)
single :: Multiplicity
single = Multiplicity 0
multiple :: Multiplicity
multiple = Multiplicity binaryOneThenZeroes
pack :: String -> Text
pack str = case metadata 0 single str of
(!totalBytes,!totalMult) ->
let !arr = runST $ do
marr <- PM.newByteArray totalBytes
let go [] !_ = return ()
go (!c : cs) !ix0 = do
ix1 <- writeChar c ix0 marr
go cs ix1
go str 0
PM.unsafeFreezeByteArray marr
in Text arr (buildZeroOffMult totalMult) totalBytes
where
metadata :: Int -> Multiplicity -> [Char] -> (Int,Multiplicity)
metadata !totalBytes !totalMult [] = (totalBytes,totalMult)
metadata !totalBytes !totalMult (!c : cs) =
let !bytes = charBytes c
!mult = if bytes < 2 then single else multiple
in metadata (bytes + totalBytes) (appendMult mult totalMult) cs
-- Result is between 1 and 4. The guards used here do not have to treat
-- surrogates as a special case.
charBytes :: Char -> Int
charBytes !c
| codepoint < 0x80 = 1
| codepoint < 0x800 = 2
| codepoint < 0x10000 = 3
| otherwise = 4
where
!codepoint = intToWord (ord c)
-- returns the new index
writeChar :: Char -> Int -> PM.MutableByteArray s -> ST s Int
writeChar !c !ix !marr
| codepoint < 0x80 = do
PM.writeByteArray marr ix (unsafeWordToWord8 codepoint)
return (ix + 1)
| codepoint < 0x800 = do
PM.writeByteArray marr ix (unsafeWordToWord8 (byteTwoOne codepoint))
PM.writeByteArray marr (ix + 1) (unsafeWordToWord8 (byteTwoTwo codepoint))
return (ix + 2)
| surrogate codepoint = do
-- Codepoint U+FFFD
PM.writeByteArray marr ix (0xEF :: Word8)
PM.writeByteArray marr (ix + 1) (0xBF :: Word8)
PM.writeByteArray marr (ix + 2) (0xBD :: Word8)
return (ix + 3)
| codepoint < 0x10000 = do
PM.writeByteArray marr ix (unsafeWordToWord8 (byteThreeOne codepoint))
PM.writeByteArray marr (ix + 1) (unsafeWordToWord8 (byteThreeTwo codepoint))
PM.writeByteArray marr (ix + 2) (unsafeWordToWord8 (byteThreeThree codepoint))
return (ix + 3)
| otherwise = do
PM.writeByteArray marr ix (unsafeWordToWord8 (byteFourOne codepoint))
PM.writeByteArray marr (ix + 1) (unsafeWordToWord8 (byteFourTwo codepoint))
PM.writeByteArray marr (ix + 2) (unsafeWordToWord8 (byteFourThree codepoint))
PM.writeByteArray marr (ix + 3) (unsafeWordToWord8 (byteFourFour codepoint))
return (ix + 4)
where
!codepoint = intToWord (ord c)
unpack :: Text -> String
unpack !t = go off
where
go :: Int -> String
go !ix0 = if ix0 < len + off
then
let !(!ix1,!c) = nextChar arr ix0
in c : go ix1
else []
!(!arr,!off,!len,!_) = textMatch t
nextChar :: ByteArray -> Int -> (Int,Char)
nextChar !arr !ix
| oneByteChar firstByte = (ix + 1, wordToChar (word8ToWord firstByte))
| twoByteChar firstByte =
let !secondByte = BA.unsafeIndex arr (ix + 1)
in (ix + 2, charFromTwoBytes firstByte secondByte)
| threeByteChar firstByte =
let !secondByte = BA.unsafeIndex arr (ix + 1)
!thirdByte = BA.unsafeIndex arr (ix + 2)
in (ix + 3, charFromThreeBytes firstByte secondByte thirdByte)
| otherwise =
let !secondByte = BA.unsafeIndex arr (ix + 1)
!thirdByte = BA.unsafeIndex arr (ix + 2)
!fourthByte = BA.unsafeIndex arr (ix + 3)
in (ix + 4, charFromFourBytes firstByte secondByte thirdByte fourthByte)
where
firstByte :: Word8
!firstByte = BA.unsafeIndex arr ix
nextCharIx :: ByteArray -> Int -> Int
nextCharIx !arr !ix
| oneByteChar firstByte = ix + 1
| twoByteChar firstByte = ix + 2
| threeByteChar firstByte = ix + 3
| otherwise = ix + 4
where
firstByte :: Word8
!firstByte = BA.unsafeIndex arr ix
moveChars ::
ByteArray -- array
-> Int -- start index
-> Int -- maximal index
-> Int -- number of characters to move through
-> Int -- end index
moveChars !arr !start0 !maxIndex !n0 = go start0 n0
where
go :: Int -> Int -> Int
go !ix !n = if n > 0 && ix < maxIndex
then go (nextCharIx arr ix) (n - 1)
else ix
countChars ::
ByteArray -- array
-> Int -- start index
-> Int -- maximal index
-> Int -- number of characters
countChars !arr !start0 !maxIndex = go start0 0
where
go :: Int -> Int -> Int
go !ix !acc = if ix < maxIndex
then go (nextCharIx arr ix) (acc + 1)
else acc
oneByteChar :: Word8 -> Bool
oneByteChar w = w .&. 0b10000000 == 0
twoByteChar :: Word8 -> Bool
twoByteChar w = w .&. 0b11100000 == 0b11000000
threeByteChar :: Word8 -> Bool
threeByteChar w = w .&. 0b11110000 == 0b11100000
charFromTwoBytes :: Word8 -> Word8 -> Char
charFromTwoBytes w1 w2 = wordToChar $
unsafeShiftL (word8ToWord w1 .&. 0b00011111) 6 .|.
(word8ToWord w2 .&. 0b00111111)
charFromThreeBytes :: Word8 -> Word8 -> Word8 -> Char
charFromThreeBytes w1 w2 w3 = wordToChar $
unsafeShiftL (word8ToWord w1 .&. 0b00001111) 12 .|.
unsafeShiftL (word8ToWord w2 .&. 0b00111111) 6 .|.
(word8ToWord w3 .&. 0b00111111)
charFromFourBytes :: Word8 -> Word8 -> Word8 -> Word8 -> Char
charFromFourBytes w1 w2 w3 w4 = wordToChar $
unsafeShiftL (word8ToWord w1 .&. 0b00000111) 18 .|.
unsafeShiftL (word8ToWord w2 .&. 0b00111111) 12 .|.
unsafeShiftL (word8ToWord w3 .&. 0b00111111) 6 .|.
(word8ToWord w4 .&. 0b00111111)
-- precondition: codepoint is less than 0x800
byteTwoOne :: Word -> Word
byteTwoOne w = unsafeShiftR w 6 .|. 0b11000000
byteTwoTwo :: Word -> Word
byteTwoTwo w = (w .&. 0b00111111) .|. 0b10000000
-- precondition: codepoint is less than 0x1000
byteThreeOne :: Word -> Word
byteThreeOne w = unsafeShiftR w 12 .|. 0b11100000
byteThreeTwo :: Word -> Word
byteThreeTwo w = (0b00111111 .&. unsafeShiftR w 6) .|. 0b10000000
byteThreeThree :: Word -> Word
byteThreeThree w = (w .&. 0b00111111) .|. 0b10000000
-- precondition: codepoint is less than 0x110000
byteFourOne :: Word -> Word
byteFourOne w = unsafeShiftR w 18 .|. 0b11110000
byteFourTwo :: Word -> Word
byteFourTwo w = (0b00111111 .&. unsafeShiftR w 12) .|. 0b10000000
byteFourThree :: Word -> Word
byteFourThree w = (0b00111111 .&. unsafeShiftR w 6) .|. 0b10000000
byteFourFour :: Word -> Word
byteFourFour w = (0b00111111 .&. w) .|. 0b10000000
word8ToWord :: Word8 -> Word
word8ToWord = fromIntegral
intToWord :: Int -> Word
intToWord = fromIntegral
wordToInt :: Word -> Int
wordToInt = fromIntegral
unsafeWordToWord8 :: Word -> Word8
unsafeWordToWord8 (W# w) = W8# w
wordToChar :: Word -> Char
wordToChar w = chr (fromIntegral w)
empty :: Text
empty = Text BA.empty 0 0
breakChar :: Char -> Text -> (Text,Text)
breakChar !c !t
| codepoint < 0x80 = breakOnByte1 (unsafeWordToWord8 codepoint) t
| codepoint < 0x800 = case findBytePair off len (unsafeWordToWord8 (byteTwoOne codepoint)) (unsafeWordToWord8 (byteTwoTwo codepoint)) arr of
Nothing -> (t,empty)
Just ix -> (Text arr (buildOffMult off mult) (ix - off), Text arr (buildOffMult ix mult) (len + off - ix))
| surrogate codepoint = case findByteTriple off len 0xEF 0xBF 0xBD arr of
Nothing -> (t,empty)
Just ix -> (Text arr (buildOffMult off mult) (ix - off), Text arr (buildOffMult ix mult) (len + off - ix))
| codepoint < 0x10000 = case findByteTriple off len (unsafeWordToWord8 (byteThreeOne codepoint)) (unsafeWordToWord8 (byteThreeTwo codepoint)) (unsafeWordToWord8 (byteThreeThree codepoint)) arr of
Nothing -> (t,empty)
Just ix -> (Text arr (buildOffMult off mult) (ix - off), Text arr (buildOffMult ix mult) (len + off - ix))
| otherwise = case findByteQuadruple off len (unsafeWordToWord8 (byteFourOne codepoint)) (unsafeWordToWord8 (byteFourTwo codepoint)) (unsafeWordToWord8 (byteFourThree codepoint)) (unsafeWordToWord8 (byteFourFour codepoint)) arr of
Nothing -> (t,empty)
Just ix -> (Text arr (buildOffMult off mult) (ix - off), Text arr (buildOffMult ix mult) (len + off - ix))
where
!codepoint = intToWord (ord c)
!(!arr,!off,!len,!mult) = textMatch t
findBytePair :: Int -> Int -> Word8 -> Word8 -> ByteArray -> Maybe Int
findBytePair off0 len0 w1 w2 arr = go off0 (off0 + len0 - 1)
where
go :: Int -> Int -> Maybe Int
go !ix !end = if ix < end
then if PM.indexByteArray arr ix == w1 && PM.indexByteArray arr (ix + 1) == w2
then Just ix
else go (ix + 1) end
else Nothing
findByteTriple :: Int -> Int -> Word8 -> Word8 -> Word8 -> ByteArray -> Maybe Int
findByteTriple off0 len0 w1 w2 w3 arr = go off0 (off0 + len0 - 2)
where
go :: Int -> Int -> Maybe Int
go !ix !end = if ix < end
then if PM.indexByteArray arr ix == w1 && PM.indexByteArray arr (ix + 1) == w2 && PM.indexByteArray arr (ix + 2) == w3
then Just ix
else go (ix + 1) end
else Nothing
findByteQuadruple :: Int -> Int -> Word8 -> Word8 -> Word8 -> Word8 -> ByteArray -> Maybe Int
findByteQuadruple off0 len0 w1 w2 w3 w4 arr = go off0 (off0 + len0 - 3)
where
go :: Int -> Int -> Maybe Int
go !ix !end = if ix < end
then if PM.indexByteArray arr ix == w1 && PM.indexByteArray arr (ix + 1) == w2 && PM.indexByteArray arr (ix + 2) == w3 && PM.indexByteArray arr (ix + 3) == w4
then Just ix
else go (ix + 1) end
else Nothing
-- precondition: the Word is less than 0x80
breakOnByte1 :: Word8 -> Text -> (Text,Text)
breakOnByte1 !w !t =
let !(!arr,!off,!len,!mult) = textMatch t
in case BAW.findByte off len w arr of
Nothing -> (t,empty)
Just !ix -> (dwindle (Text arr (buildOffMult off mult) (ix - off)), Text arr (buildOffMult ix mult) (len + off - ix))
dwindle :: Text -> Text
dwindle t@(Text _ _ !len) = if len > 0 then t else empty
buildOffMult :: Int -> Multiplicity -> Word
buildOffMult i (Multiplicity x) = intToWord i .|. x
buildZeroOffMult :: Multiplicity -> Word
buildZeroOffMult (Multiplicity w) = w
textMatch :: Text -> (ByteArray,Int,Int,Multiplicity)
textMatch (Text arr offMult len) =
( arr
, wordToInt (binaryZeroThenOnes .&. offMult)
, len
, Multiplicity (offMult .&. binaryOneThenZeroes)
)
binaryOneThenZeroes :: Word
binaryOneThenZeroes = maxBound - div (maxBound :: Word) 2
binaryZeroThenOnes :: Word
binaryZeroThenOnes = div (maxBound :: Word) 2
surrogate :: Word -> Bool
surrogate codepoint = codepoint >= 0xD800 && codepoint < 0xE000
-- Each byte in the word that is a lowercase ascii character is turned
-- into 0x80. All other bytes become 0x00.
hasAsciiLowerArtifact :: Word -> Word
hasAsciiLowerArtifact w =
((div maxBound 255 * (127 + hi)) - (w .&. (div maxBound 255 * 127))) .&. complement w .&.
((w .&. (div maxBound 255 * 127)) + (div maxBound 255 * (127 - lo))) .&. (div maxBound 255 * 128)
where
lo = intToWord (ord 'a' - 1)
hi = intToWord (ord 'z' + 1)
-- TODO: improve this. Currently, we do not use a vectorized implementation if
-- the string doesn't start aligned on a machine word address. We can fix this
-- by padding the result to the left to line it up better. This would trade a
-- small amount of space for more speed.
{-# INLINE mapVectorizable #-}
mapVectorizable ::
(Word8 -> Word8) -- function
-> (Word -> Word) -- vectorized function variant
-> Int -- start
-> Int -- len
-> ByteArray -- array
-> ByteArray
mapVectorizable !func !funcMach !start !len !arr = runST action
where
action :: forall s. ST s ByteArray
action = do
marr <- PM.newByteArray len
let !(!quotStart,!remStart) = quotRem start (PM.sizeOf (undefined :: Word))
go :: Int -> Int -> ST s ()
go !ix !end = if ix < end
then do
PM.writeByteArray marr ix (func (PM.indexByteArray arr (start + ix)))
go (ix + 1) end
else return ()
goMach :: Int -> Int -> ST s ()
goMach !ix !end = if ix < end
then do
PM.writeByteArray marr ix (funcMach (PM.indexByteArray arr (quotStart + ix)))
goMach (ix + 1) end
else return ()
if remStart == 0
then do
let !lenQuotient = quot len (PM.sizeOf (undefined :: Word))
goMach 0 lenQuotient
go (lenQuotient * PM.sizeOf (undefined :: Word)) len
else go 0 len
PM.unsafeFreezeByteArray marr
toUpperAsciiWord8 :: Word8 -> Word8
toUpperAsciiWord8 w = if word8ToWord w - intToWord (ord 'a') < 26
then w - 0x20
else w
toUpperAsciiWord :: Word -> Word
toUpperAsciiWord w = w - unsafeShiftR (hasAsciiLowerArtifact w) 2
toUpperAscii :: Int -> Int -> ByteArray -> ByteArray
toUpperAscii !off !len !arr = mapVectorizable toUpperAsciiWord8 toUpperAsciiWord off len arr
toUpper :: Text -> Text
toUpper t = if mult == single
then Text (toUpperAscii off len arr) (buildZeroOffMult single) len
else map Data.Char.toUpper t
where
!(!arr,!off,!len,!mult) = textMatch t
map :: (Char -> Char) -> Text -> Text
map f !t = runST action
where
!(!arr,!off,!len,!_) = textMatch t
action :: ST s Text
action = do
marr0 <- PM.newByteArray (len + 3)
let go :: Int -> Int -> Int -> Multiplicity -> MutableByteArray s -> ST s (Int,MutableByteArray s,Multiplicity)
go !ixSrc !ixDst !marrLen !mult !marr = if ixSrc < off + len
then do
let !(!ixSrcNext,!c) = nextChar arr ixSrc
!c' = f c
-- It is disappointing that this is handled this
-- way. Reconsider this later.
!newMult = appendMult mult (if ixSrcNext - ixSrc > 1 then multiple else single)
if ixDst < marrLen - 3
then do
ixDstNext <- writeChar c' ixDst marr
go ixSrcNext ixDstNext marrLen newMult marr
else do
let newMarrLen = marrLen * 2
newMarr <- PM.newByteArray newMarrLen
PM.copyMutableByteArray newMarr 0 marr 0 marrLen -- possible minus 3?
ixDstNext <- writeChar c' ixDst newMarr
go ixSrcNext ixDstNext newMarrLen newMult newMarr
else return (ixDst,marr,mult)
(finalLen,finalMarr,finalMult) <- go off 0 (len + 3) single marr0
newArr <- PM.unsafeFreezeByteArray finalMarr
return (Text newArr (buildZeroOffMult finalMult) finalLen)
-- | /O(n)/ 'take' @n xs@ returns the prefix of @xs@ of length @n@. It returns
-- @xs@ instead when @n > 'length' xs@. On text containing only ASCII characters,
-- the complexity of this function is reduced to /O(1)/.
take :: Int -> Text -> Text
take !n !t@(Text _ !offMult _) = if n < 1
then empty
else if mult == single
then if n < len
then Text arr offMult n
else t
else if n < len * 4
then Text arr offMult (moveChars arr off (off + len) n - off)
else t
where
!(!arr,!off,!len,!mult) = textMatch t
-- | /O(n)/ 'drop' @n xs@ returns the suffix of @xs@ after the first @n@ characters
-- have been removed. It returns @empty@ instead when @n > 'length' xs@. On text
-- containing only ASCII characters, the complexity of this function is reduced to /O(1)/.
drop :: Int -> Text -> Text
drop !n !t = if n < 1
then t
else if mult == single
then if n < len
then Text arr (buildOffMult (off + n) mult) (len - n)
else empty
else if n < len * 4
then
let !skipped = moveChars arr off (off + len) n - off
in Text arr (buildOffMult (off + skipped) mult) (len - skipped)
else empty
where
!(!arr,!off,!len,!mult) = textMatch t
length :: Text -> Int
length !t = if mult == single
then len
else countChars arr off (off + len)
where
!(!arr,!off,!len,!mult) = textMatch t
-- | /O(n)/ 'dropEnd' @n xs@ returns the prefix of @xs@ after the last @n@ characters
-- have been removed. It returns @empty@ instead when @n > 'length' xs@. On text
-- containing only ASCII characters, the complexity of this function is reduced to /O(1)/.
dropEnd :: Int -> Text -> Text
-- Note: There is a way to implement this that is more efficient. It would
-- required scanning UTF-8 encoded text backwards, which seems annoying
-- to do.
dropEnd !n !t = take (length t - n) t
decodeAscii :: Bytes -> Maybe Text
decodeAscii b = case decodeResumeAscii b of
(# t, (# | (# #) #) #) -> Just t
(# !_, (# !_ | #) #) -> Nothing
-- decodeAscii b@(Bytes arr off len) = if B.isAscii b
-- then Just (Text arr (buildOffMult off single) len)
-- else Nothing
decodeResumeAscii :: Bytes -> (# Text, (# Bytes | (# #) #) #)
decodeResumeAscii (Bytes arr off len) = case BAW.findNonAscii' off len arr of
(# (# #) | #) -> (# Text arr (buildOffMult off single) len, (# | (# #) #) #)
(# | ix# #) ->
let ix = I# ix#
in (# Text arr (buildOffMult off single) ix, (# (Bytes arr ix (len + off - ix)) | #) #)
decodeUtf8 :: Bytes -> Maybe Text
decodeUtf8 b = case decodeResumeUtf8 b of
(# !_, (# !_ | | #) #) -> Nothing
(# !t, (# | (# #) | #) #) -> Just t
(# !_, (# | | !_ #) #) -> Nothing
-- decodeUtf8 :: Bytes -> Maybe Text
-- decodeUtf8 b = case decodeResumeUtf8 b of
-- (# !_, West !_ #) -> Nothing
-- (# !t, Center () #) -> Just t
-- (# !_, East !_ #) -> Nothing
data Choice a b c = West a | Center b | East c
decodeResumeUtf8 ::
Bytes
-> (# Text, (# Bytes | (# #) | (# Word#, Word#, Word# #) #) #)
decodeResumeUtf8 (Bytes arr off len) =
let !(# !ascii, !r #) = BAW.isUtf8 off len arr
!modifiedArr = case ascii of
1## -> runST $ do
marr <- PM.newByteArray len
PM.copyByteArray marr 0 arr off len
replaceSurrogates 0 len marr
PM.unsafeFreezeByteArray marr
_ -> arr
!mult = case ascii of
1## -> multiple
_ -> Multiplicity (W# ascii)
in case r of
(# ixFailure# | | #) ->
let !ixFailure = I# ixFailure#
in (# Text modifiedArr (buildOffMult off mult) ixFailure, (# (Bytes arr ixFailure (len + off - ixFailure)) | | #) #)
(# | (# #) | #) -> (# Text modifiedArr (buildOffMult off mult) len, (# | (# #) | #) #)
(# | | (# w1, w2, w3 #) #) -> (# Text modifiedArr (buildOffMult off mult) len , (# | | (# w1, w2, w3 #) #) #)
replaceSurrogates :: forall s. Int -> Int -> MutableByteArray s -> ST s ()
replaceSurrogates start len marr = go start where
go :: Int -> ST s ()
go !ix = if ix < len - 2
then do
!w1 <- PM.readByteArray marr ix
!w2 <- PM.readByteArray marr (ix + 1)
!w3 <- PM.readByteArray marr (ix + 2)
if threeByteChar w1 && surrogate (codepointFromThreeBytes w1 w2 w3)
then do
-- Codepoint U+FFFD
PM.writeByteArray marr ix (0xEF :: Word8)
PM.writeByteArray marr (ix + 1) (0xBF :: Word8)
PM.writeByteArray marr (ix + 2) (0xBD :: Word8)
go (ix + 3)
else go (ix + 1)
else return ()
codepointFromThreeBytes :: Word8 -> Word8 -> Word8 -> Word
codepointFromThreeBytes w1 w2 w3 =
unsafeShiftL (word8ToWord w1 .&. 0b00001111) 12 .|.
unsafeShiftL (word8ToWord w2 .&. 0b00111111) 6 .|.
(word8ToWord w3 .&. 0b00111111)
convertTuple :: (# (# #) | (# Word#, Word#, Word# #) #) -> Either () (Word,Word,Word)
convertTuple (# (# #) | #) = Left ()
convertTuple (# | w #) = Right (convertWordTuple w)
convertWordTuple :: (# Word#, Word#, Word# #) -> (Word,Word,Word)
convertWordTuple (# a,b,c #) = ( W# a, W# b, W# c )
encodeUtf8 :: Text -> Bytes
encodeUtf8 t = Bytes arr off len where
!(!arr,!off,!len,!_) = textMatch t