-- |
-- Module      : Basement.String.Encoding.ASCII7
-- License     : BSD-style
-- Maintainer  : Foundation
-- Stability   : experimental
-- Portability : portable
--

{-# LANGUAGE MagicHash #-}

module Basement.String.Encoding.ASCII7
    ( ASCII7(..)
    , ASCII7_Invalid(..)
    ) where

import Basement.Compat.Base
import Basement.Compat.Primitive
import Basement.Types.OffsetSize
import Basement.Numerical.Additive
import Basement.Monad
import Basement.Bits

import GHC.Prim (int2Word#, ord#)
import GHC.Word
import GHC.Types
import Basement.UArray
import Basement.UArray.Mutable (MUArray)
import Basement.MutableBuilder

import Basement.String.Encoding.Encoding

-- | validate a given byte is within ASCII characters encoring size
--
-- This function check the 8th bit is set to 0
--
isAscii :: Word8 -> Bool
isAscii :: Word8 -> Bool
isAscii Word8
w = (Word8
w Word8 -> Word8 -> Word8
forall bits. BitOps bits => bits -> bits -> bits
.&. Word8
0x80) Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0
{-# INLINE isAscii #-}

data ASCII7_Invalid
    = ByteOutOfBound Word8
    | CharNotAscii   Char
  deriving (Typeable, Int -> ASCII7_Invalid -> ShowS
[ASCII7_Invalid] -> ShowS
ASCII7_Invalid -> String
(Int -> ASCII7_Invalid -> ShowS)
-> (ASCII7_Invalid -> String)
-> ([ASCII7_Invalid] -> ShowS)
-> Show ASCII7_Invalid
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ASCII7_Invalid -> ShowS
showsPrec :: Int -> ASCII7_Invalid -> ShowS
$cshow :: ASCII7_Invalid -> String
show :: ASCII7_Invalid -> String
$cshowList :: [ASCII7_Invalid] -> ShowS
showList :: [ASCII7_Invalid] -> ShowS
Show, ASCII7_Invalid -> ASCII7_Invalid -> Bool
(ASCII7_Invalid -> ASCII7_Invalid -> Bool)
-> (ASCII7_Invalid -> ASCII7_Invalid -> Bool) -> Eq ASCII7_Invalid
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ASCII7_Invalid -> ASCII7_Invalid -> Bool
== :: ASCII7_Invalid -> ASCII7_Invalid -> Bool
$c/= :: ASCII7_Invalid -> ASCII7_Invalid -> Bool
/= :: ASCII7_Invalid -> ASCII7_Invalid -> Bool
Eq)
instance Exception ASCII7_Invalid

data ASCII7 = ASCII7

instance Encoding ASCII7 where
    type Unit ASCII7 = Word8
    type Error ASCII7 = ASCII7_Invalid
    encodingNext :: ASCII7
-> (Offset (Unit ASCII7) -> Unit ASCII7)
-> Offset (Unit ASCII7)
-> Either (Error ASCII7) (Char, Offset (Unit ASCII7))
encodingNext  ASCII7
_ = (Offset Word8 -> Word8)
-> Offset Word8 -> Either ASCII7_Invalid (Char, Offset Word8)
(Offset (Unit ASCII7) -> Unit ASCII7)
-> Offset (Unit ASCII7)
-> Either (Error ASCII7) (Char, Offset (Unit ASCII7))
next
    encodingWrite :: forall (st :: * -> *) err.
(PrimMonad st, Monad st) =>
ASCII7
-> Char
-> Builder
     (UArray (Unit ASCII7))
     (MUArray (Unit ASCII7))
     (Unit ASCII7)
     st
     err
     ()
encodingWrite ASCII7
_ = Char -> Builder (UArray Word8) (MUArray Word8) Word8 st err ()
Char
-> Builder
     (UArray (Unit ASCII7))
     (MUArray (Unit ASCII7))
     (Unit ASCII7)
     st
     err
     ()
forall (st :: * -> *) err.
(PrimMonad st, Monad st) =>
Char -> Builder (UArray Word8) (MUArray Word8) Word8 st err ()
write

-- | consume an Ascii7 char and return the Unicode point and the position
-- of the next possible Ascii7 char
--
next :: (Offset Word8 -> Word8)
          -- ^ method to access a given byte
     -> Offset Word8
          -- ^ index of the byte
     -> Either ASCII7_Invalid (Char, Offset Word8)
          -- ^ either successfully validated the ASCII char and returned the
          -- next index or fail with an error
next :: (Offset Word8 -> Word8)
-> Offset Word8 -> Either ASCII7_Invalid (Char, Offset Word8)
next Offset Word8 -> Word8
getter Offset Word8
off
    | Word8 -> Bool
isAscii Word8
w8 = (Char, Offset Word8) -> Either ASCII7_Invalid (Char, Offset Word8)
forall a b. b -> Either a b
Right (Word8# -> Char
toChar Word8#
w, Offset Word8
off Offset Word8 -> Offset Word8 -> Offset Word8
forall a. Additive a => a -> a -> a
+ Offset Word8
1)
    | Bool
otherwise  = ASCII7_Invalid -> Either ASCII7_Invalid (Char, Offset Word8)
forall a b. a -> Either a b
Left (ASCII7_Invalid -> Either ASCII7_Invalid (Char, Offset Word8))
-> ASCII7_Invalid -> Either ASCII7_Invalid (Char, Offset Word8)
forall a b. (a -> b) -> a -> b
$ Word8 -> ASCII7_Invalid
ByteOutOfBound Word8
w8
  where
    !w8 :: Word8
w8@(W8# Word8#
w) = Offset Word8 -> Word8
getter Offset Word8
off
    toChar :: Word8# -> Char
    toChar :: Word8# -> Char
toChar Word8#
a = Char# -> Char
C# (Word8# -> Char#
word8ToChar# Word8#
w)

-- Write ascii char
--
-- > build 64 $ sequence_ write "this is a simple list of char..."
--
write :: (PrimMonad st, Monad st)
      => Char
           -- ^ expecting it to be a valid Ascii character.
           -- otherwise this function will throw an exception
      -> Builder (UArray Word8) (MUArray Word8) Word8 st err ()
write :: forall (st :: * -> *) err.
(PrimMonad st, Monad st) =>
Char -> Builder (UArray Word8) (MUArray Word8) Word8 st err ()
write Char
c
    | Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
< Int -> Char
forall a. Enum a => Int -> a
toEnum Int
0x80 = Word8 -> Builder (UArray Word8) (MUArray Word8) Word8 st err ()
forall ty (state :: * -> *) err.
(PrimType ty, PrimMonad state) =>
ty -> Builder (UArray ty) (MUArray ty) ty state err ()
builderAppend (Word8 -> Builder (UArray Word8) (MUArray Word8) Word8 st err ())
-> Word8 -> Builder (UArray Word8) (MUArray Word8) Word8 st err ()
forall a b. (a -> b) -> a -> b
$ Char -> Word8
w8 Char
c
    | Bool
otherwise       = ASCII7_Invalid
-> Builder (UArray Word8) (MUArray Word8) Word8 st err ()
forall a e. Exception e => e -> a
throw (ASCII7_Invalid
 -> Builder (UArray Word8) (MUArray Word8) Word8 st err ())
-> ASCII7_Invalid
-> Builder (UArray Word8) (MUArray Word8) Word8 st err ()
forall a b. (a -> b) -> a -> b
$ Char -> ASCII7_Invalid
CharNotAscii Char
c
  where
    w8 :: Char -> Word8
    w8 :: Char -> Word8
w8 (C# Char#
ch) = Word8# -> Word8
W8# (Word# -> Word8#
wordToWord8# (Int# -> Word#
int2Word# (Char# -> Int#
ord# Char#
ch)))