packages feed

kleene-0.2: tests/kleene-utf8.hs

{-# LANGUAGE CPP #-}
module Main (main) where

import Data.Bits        (shiftR, (.&.), (.|.))
import Data.Char        (ord)
import Data.Semigroup   ((<>))
import Data.Word        (Word8)
import Test.Tasty       (defaultMain, testGroup)
import Test.Tasty.HUnit (assertBool, testCase)

import Kleene
import Kleene.DFA (fromRE, toRE)

#if MIN_VERSION_bytestring(0,11,2)
import Test.Tasty.QuickCheck (label, testProperty, (===))

import qualified Data.ByteString as BS
#endif

main :: IO ()
main = do
    -- print utf8char2
    -- print utf8char3
    -- print utf8char4
    defaultMain $ testGroup "kleene-utf8"
        [ testCase "brute ≃ brute'" $ do
            assertBool "not equiv" (equivalent utf8char1 utf8char2)
        , testCase "brute' ≃ manual1" $ do
            assertBool "not equiv" (equivalent utf8char2 utf8char3)
        , testCase "manual1 ≃ manual2" $ do
            assertBool "not equiv" (equivalent utf8char3 utf8char4)
        , testCase "brute ≃ manual2" $ do
            assertBool "not equiv" (equivalent utf8char1 utf8char4)

#if MIN_VERSION_bytestring(0,11,2)
        , testProperty "isValidUtf8" $ \w8s ->
            let bs = BS.pack w8s
                isValid = BS.isValidUtf8 bs
            in label (show isValid) $ isValid === match (star utf8char4) w8s
#endif
        ]

-------------------------------------------------------------------------------
-- Bruteforce definition
-------------------------------------------------------------------------------

utf8char1 :: RE Word8
utf8char1 = unions
    [ string (encodeStringUtf8 [c])
    | c <- [ '\0' .. maxBound ]
    ]

-------------------------------------------------------------------------------
-- Derived definition
-------------------------------------------------------------------------------

utf8charDFA1 :: DFA Word8
utf8charDFA1 = fromRE utf8char1

utf8char2 :: RE Word8
utf8char2 = toRE utf8charDFA1

-------------------------------------------------------------------------------
-- Written out definition
-------------------------------------------------------------------------------

utf8char3 :: RE Word8
utf8char3 = unions
    [ charRange 0x00 0x7F
    , sub1 <> charRange 0x80 0xBF
    ]
  where
    sub1 = unions
        [ charRange 0xC2 0xDF
        , charRange 0xE0 0xE0 <> charRange 0xa0 0xBF
        , charRange 0xED 0xED <> charRange 0x80 0x9F
        , sub2 <> charRange 0x80 0xBF
        ]

    sub2 = unions
        [ charRange 0xE1 0xEC
        , charRange 0xEE 0xEF
        , charRange 0xF0 0xF0 <> charRange 0x90 0xBF
        , charRange 0xF1 0xF3 <> charRange 0x80 0xBF
        , charRange 0xF4 0xF4 <> charRange 0x80 0x8f
        ]

-------------------------------------------------------------------------------
-- Manual definition, how human would written it
-------------------------------------------------------------------------------

utf8char4 :: RE Word8
utf8char4 = unions
    [ charRange 0x00 0x7F
    , charRange 0xC2 0xDF <> charRange 0x80 0xBF
    , charRange 0xE0 0xE0 <> charRange 0xa0 0xBF <> charRange 0x80 0xBF
    , charRange 0xE1 0xEC <> charRange 0x80 0xBF <> charRange 0x80 0xBF
    , charRange 0xED 0xED <> charRange 0x80 0x9F <> charRange 0x80 0xBF
    , charRange 0xEE 0xEF <> charRange 0x80 0xBF <> charRange 0x80 0xBF
    , charRange 0xF0 0xF0 <> charRange 0x90 0xBF <> charRange 0x80 0xBF <> charRange 0x80 0xBF
    , charRange 0xF1 0xF3 <> charRange 0x80 0xBF <> charRange 0x80 0xBF <> charRange 0x80 0xBF
    , charRange 0xF4 0xF4 <> charRange 0x80 0x8f <> charRange 0x80 0xBF <> charRange 0x80 0xBF
    ]

-------------------------------------------------------------------------------
-- UTF8 encoding
-------------------------------------------------------------------------------

encodeStringUtf8 :: String -> [Word8]
encodeStringUtf8 []        = []
encodeStringUtf8 (c:cs)
  | c <= '\x07F' = w8
                 : encodeStringUtf8 cs
  | c <= '\x7FF' = (0xC0 .|.  w8ShiftR  6          )
                 : (0x80 .|. (w8          .&. 0x3F))
                 : encodeStringUtf8 cs
  | c <= '\xD7FF'= (0xE0 .|.  w8ShiftR 12          )
                 : (0x80 .|. (w8ShiftR  6 .&. 0x3F))
                 : (0x80 .|. (w8          .&. 0x3F))
                 : encodeStringUtf8 cs
  | c <= '\xDFFF'= 0xEF : 0xBF : 0xBD -- U+FFFD
                 : encodeStringUtf8 cs
  | c <= '\xFFFF'= (0xE0 .|.  w8ShiftR 12          )
                 : (0x80 .|. (w8ShiftR  6 .&. 0x3F))
                 : (0x80 .|. (w8          .&. 0x3F))
                 : encodeStringUtf8 cs
  | otherwise    = (0xf0 .|.  w8ShiftR 18          )
                 : (0x80 .|. (w8ShiftR 12 .&. 0x3F))
                 : (0x80 .|. (w8ShiftR  6 .&. 0x3F))
                 : (0x80 .|. (w8          .&. 0x3F))
                 : encodeStringUtf8 cs
  where
    w8 = fromIntegral (ord c) :: Word8
    w8ShiftR :: Int -> Word8
    w8ShiftR = fromIntegral . shiftR (ord c)