willow-0.1.0.0: src/Web/Willow/Common/Encoding/ShiftJis.hs
{-|
Description: Character translation functions to and from the Shift_JIS 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.ShiftJis
( decoder
, encoder
) where
import qualified Control.Applicative as A
import qualified Data.ByteString.Short as BS.SH
import qualified Data.Char as C
import qualified Data.Tuple as U
import qualified Data.Word as W
import Web.Willow.Common.Parser
import Web.Willow.Common.Parser.Switch
import Web.Willow.Common.Encoding.Common
import Web.Willow.Common.Encoding.EucJp
( decodeIndex0208, encodeIndex0208 )
-- | __Encoding:__
-- @[Shift_JIS decoder]
-- (https://encoding.spec.whatwg.org/#shift_jis-decoder)@
--
-- Decodes a 'Char' from a binary stream encoded with the 'ShiftJis' encoding
-- scheme, or returns 'Left' if the stream starts with an invalid byte
-- sequence.
decoder :: TextBuilder
decoder = next >>= switch
[ If isAsciiByte toUnicode1
, If_ (== 0x80) $ emit [0x80] '\x80'
, If (<= 0x9F) decoder'
, If (range 0xA1 0xDF) $ \b ->
emit [b] . toEnum $ 0xFEC0 + fromIntegral b
, If (range 0xE0 0xFC) decoder'
, Else decoderFailure1
]
-- | __Encoding:__
-- @[Shift_JIS decoder]
-- (https://encoding.spec.whatwg.org/#shift_jis-decoder)@
-- step 3
--
-- Process a double-byte sequence according to the 'ShiftJis' encoding scheme.
decoder' :: W.Word8 -> TextBuilder
decoder' lead = next >>= switch
[ If isAsciiByte $ decodeIndex lead
, Else $ decodeIndex lead
]
-- | __Encoding:__
-- @[Shift_JIS decoder]
-- (https://encoding.spec.whatwg.org/#shift_jis-decoder)@
-- step 3, substeps 3-6
--
-- Given a two-byte sequence, calculate and return its associated character
-- representation in the 'ShiftJis' encoding scheme.
--
-- Fails if no such sequence is defined.
decodeIndex :: W.Word8 -> W.Word8 -> TextBuilder
decodeIndex lead trail = case lead' * 188 + trail' of
educ | range 8836 10715 educ ->
emit [lead, trail] . toEnum $ 0xBD7C + fromIntegral educ
pointer ->
maybe failure (emit [lead, trail]) $ decodeIndex0208 pointer
where lead'
| lead <= 0xA0 = fromIntegral lead - 0x81
| otherwise = fromIntegral lead - 0xC1
trail'
| trail <= 0x7F = fromIntegral trail - 0x40
| otherwise = fromIntegral trail - 0x41
failure
| trail <= 0x7F = A.empty
| otherwise = decoderFailure [lead, trail]
-- | __Encoding:__
-- @[Shift_JIS encoder]
-- (https://encoding.spec.whatwg.org/#shift_jis-encoder)@
--
-- Encode the first 'Char' in a string according to the 'ShiftJis' encoding
-- scheme, or return that same character if that scheme doesn't define a binary
-- representation for it.
encoder :: BinaryBuilder
encoder = next >>= switch
[ If C.isAscii fromAscii
, If_ (== '\x80') $ return (pure $ BS.SH.pack [0x80])
, If_ (== '\xA5') $ return (pure $ BS.SH.pack [0x5C])
, If_ (== '\x203E') $ return (pure $ BS.SH.pack [0x7E])
, If_ (== '\x2212') $ encodeIndex '\xFF0D'
, If (range '\xFF61' '\xFF9F') $
return . pure . BS.SH.pack . (: []) . fromIntegral . subtract 0xFEC0 . fromEnum
, Else encodeIndex
]
-- | __Encoding:__
-- @[Shift_JIS encoder]
-- (https://encoding.spec.whatwg.org/#shift_jis-encoder)@
-- steps 7-13
--
-- Look for the binary representation of a given character in the 'ShiftJis'
-- encoding.
encodeIndex :: Char -> BinaryBuilder
encodeIndex char = case encodeIndexShiftJis char of
Just i ->
let (lead, trail) = divMod i 188
lead' = lead + if lead < 0x1F then 0x81 else 0xC1
trail' = trail + if trail < 0x3F then 0x40 else 0x41
in return . pure $ BS.SH.pack [fromIntegral lead', fromIntegral trail']
Nothing -> encoderFailure char
-- | Given a character, try to find the index value corresponding to it in the
-- 'ShiftJis' encoding scheme.
encodeIndexShiftJis :: Char -> Maybe Word
encodeIndexShiftJis char = encodeIndex0208 char >>= redirect
where redirect i
| i >= 8272 && i <= 8835 =
lookupMemoizedIndex encodeIndexShiftJisM Nothing char readEncodeIndexShiftJis
| otherwise = Just i
-- | Memoization table to save lookup time in the over-large 'ShiftJis' index.
encodeIndexShiftJisM :: EncoderMemoTable
encodeIndexShiftJisM = newMemoizationTable
{-# NOINLINE encodeIndexShiftJisM #-}
-- | __Encoding:__
-- @[index Shift_JIS pointer]
-- (https://encoding.spec.whatwg.org/#index-shift_jis-pointer)@
--
-- Find the offset of a given character in the 'ShiftJis' index. Note that
-- this is a heavy function, and should be cached whenever possible.
readEncodeIndexShiftJis :: Char -> Maybe Word
readEncodeIndexShiftJis char = search char . map U.swap $ loadIndex' filterIndex "jis0208"
where filterIndex (i, _) = i < 8272 || i > 8835