packages feed

willow-0.1.0.0: src/Web/Willow/Common/Encoding/Utf16.hs

{-|
Description:    Character translation functions to and from the UTF-16 encoding scheme.

Copyright:      (c) 2020 Sam May
License:        MPL-2.0
Maintainer:     ag.eitilt@gmail.com

Stability:      experimental
Portability:    portable
-}
module Web.Willow.Common.Encoding.Utf16
    ( -- * Decoders
      decoderBigEndian
    , decoderLittleEndian
      -- * BOM Detection
    , byteOrderMarkBigEndian
    , byteOrderMarkLittleEndian
    ) where


import qualified Control.Applicative as A

import qualified Data.Bits as B
import qualified Data.ByteString as BS
import qualified Data.Word as W

import Data.Functor ( ($>) )

import Web.Willow.Common.Encoding.Common
import Web.Willow.Common.Parser


-- | The binary encoding of a byte-order mark character in UTF-16 with
-- big-endian layout.
-- 
-- Fails if the stream does not start with the exact sequence @[0xFE, 0xFF]@.
-- If successful, always returns 'Utf16be'.
byteOrderMarkBigEndian
    :: (A.Alternative gather, Monad gather)
    => ParserT BS.ByteString gather Encoding
byteOrderMarkBigEndian = chunk (BS.pack [0xFE, 0xFF]) $> Utf16be

-- | The binary encoding of a byte-order mark character in UTF-16 with
-- little-endian layout.
-- 
-- Fails if the stream does not start with the exact sequence @[0xFF, 0xFE]@.
-- If successful, always returns 'Utf16le'.
byteOrderMarkLittleEndian
    :: (A.Alternative gather, Monad gather)
    => ParserT BS.ByteString gather Encoding
byteOrderMarkLittleEndian = chunk (BS.pack [0xFF, 0xFE]) $> Utf16le


-- | __Encoding:__
--      @[UTF-16BE decoder]
--      (https://encoding.spec.whatwg.org/#utf-16be-decoder)@
-- 
-- Decodes a 'Char' from a binary stream encoded with the 'Utf16be' encoding
-- scheme, or returns 'Left' if the stream starts with an invalid byte
-- sequence.
decoderBigEndian :: TextBuilder
decoderBigEndian = decoder True

-- | __Encoding:__
--      @[UTF-16LE decoder]
--      (https://encoding.spec.whatwg.org/#utf-16le-decoder)@
-- 
-- Decodes a 'Char' from a binary stream encoded with the 'Utf16le' encoding
-- scheme, or returns 'Left' if the stream starts with an invalid byte
-- sequence.
decoderLittleEndian :: TextBuilder
decoderLittleEndian = decoder False


-- | __Encoding:__
--      @[shared UTF-16 decoder]
--      (https://encoding.spec.whatwg.org/#shared-utf-16-decoder)@
-- 
-- Decodes a 'Char' from a binary stream encoded with either UTF-16 encoding
-- scheme, or returns 'Left' if the stream starts with an invalid byte
-- sequence.
decoder
    :: Bool
        -- ^ Endianness of the stream; 'True' is 'Utf16be', 'False' is 'Utf16le'.
    -> TextBuilder
decoder bigEndian = next >>= \b1 -> next >>= \b2 ->
    case decodeUnit bigEndian b1 b2 of
        unit | range 0xDC00 0xDFFF unit -> decoderFailure [b1, b2]
        unit | range 0xD800 0xDBFF unit -> decoder' bigEndian unit b1 b2
        unit -> toUnicode [b1, b2] unit

-- | __Encoding:__
--      @[shared UTF-16 decoder]
--      (https://encoding.spec.whatwg.org/#shared-utf-16-decoder)@
--      step 5
-- 
-- Process a character encoded as a surrogate pair according to the UTF-16
-- encoding scheme.
decoder'
    :: Bool
        -- ^ Endianness of the stream; 'True' is 'Utf16be', 'False' is 'Utf16le'.
    -> Word
        -- ^ The code point of the first surrogate in the pair.
    -> W.Word8
        -- ^ The first byte of the entire four-byte sequence, for error reporting.
    -> W.Word8
        -- ^ The second byte of the entire four-byte sequence, for error reporting.
    -> TextBuilder
decoder' bigEndian unit1 b1 b2 = next >>= \b3 -> next >>= \b4 ->
    case decodeUnit bigEndian b3 b4 of
        unit2 | range 0xDC00 0xDFFF unit2 -> toUnicode [b1, b2, b3, b4] $
            0x10000 + B.shiftL (unit1 - 0xD800) 10 + unit2 - 0xDC00
        _ -> push b4 *> push b3 *> decoderFailure [b1, b2]

-- | __Encoding:__
--      @[shared UTF-16 decoder]
--      (https://encoding.spec.whatwg.org/#shared-utf-16-decoder)@
--      step 4
-- 
-- Join a two-byte UTF-16 sequence into a single value, according to the
-- endianness of the stream.
decodeUnit :: Bool -> W.Word8 -> W.Word8 -> Word
decodeUnit True lead trail = B.shiftL (fromIntegral lead) 8 + fromIntegral trail
decodeUnit False lead trail = B.shiftL (fromIntegral trail) 8 + fromIntegral lead