streamly-0.10.1: test/Streamly/Test/Unicode/Char.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
-- |
-- Copyright : (c) 2020 Composewell Technologies
--
-- License : BSD-3-Clause
-- Maintainer : streamly@composewell.com
-- Stability : experimental
-- Portability : GHC
--
module Streamly.Test.Unicode.Char (main) where
import Control.Monad (when)
import Data.Char (chr, isSpace, ord, toUpper)
import Data.List (intercalate, isPrefixOf)
import Data.Function ((&))
import Streamly.Internal.Unicode.Char
( NormalizationMode(NFC, NFD, NFKC, NFKD)
, normalize
)
import Streamly.Internal.Data.Stream (Stream)
import Text.Printf (printf)
import System.FilePath.Posix ((</>))
import qualified Streamly.Internal.Data.Stream as S
import qualified Streamly.Internal.Data.Fold as FL
import qualified System.Directory as Dir
chrToHex :: Char -> [Char]
chrToHex = map toUpper . printf "%.4x" . ord
strToHex :: [Char] -> String
strToHex = unwords . map chrToHex
type Text = Stream IO Char
toList = S.fold FL.toList
checkEqual :: String -> (Text -> Text) -> (Text, Text) -> IO Bool
checkEqual opName op (mc1, mc2) = do
c1 <- toList mc1
c2 <- toList mc2
opc2 <- toList $ op mc2
if c1 /= opc2 then do
putStrLn $ opName ++ " " ++ strToHex c2
++ " = " ++ strToHex opc2
++ "; Expected: " ++ strToHex c1
return False
else return True
checkOp :: String -> NormalizationMode -> [(Text, Text)] -> IO Bool
checkOp name op pairs = do
res <- mapM (checkEqual name (normalize op)) pairs
return $ and res
checkNFC :: (Text, Text, Text, Text, Text) -> IO Bool
checkNFC (c1, c2, c3, c4, c5) =
checkOp "toNFC" NFC $ map (c2, ) [c1, c2, c3] ++ map (c4, ) [c4, c5]
checkNFD :: (Text, Text, Text, Text, Text) -> IO Bool
checkNFD (c1, c2, c3, c4, c5) =
checkOp "toNFD" NFD $ map (c3, ) [c1, c2, c3] ++ map (c5, ) [c4, c5]
checkNFKC :: (Text, Text, Text, Text, Text) -> IO Bool
checkNFKC (c1, c2, c3, c4, c5) =
checkOp "toNFKC" NFKC $ map (c4,) [c1, c2, c3, c4, c5]
checkNFKD :: (Text, Text, Text, Text, Text) -> IO Bool
checkNFKD (c1, c2, c3, c4, c5) =
checkOp "toNFKD" NFKD $ map (c5,) [c1, c2, c3, c4, c5]
splitOn predicate f = S.foldManyPost (FL.takeEndBy_ predicate f)
checkAllTestCases :: Int -> String -> IO ()
checkAllTestCases lineno line = do
cs <- toList $ splitOn (== ';') FL.toList $ S.fromList line
case cs of
c1 : c2 : c3 : c4 : c5 : _ -> do
let cps = map cpToText [c1, c2, c3, c4, c5]
mapM_ (checkOneTestCase cps)
[checkNFD, checkNFKD, checkNFC, checkNFKC]
_ -> error $ "Unrecognized line: " ++ line
where
cpToText xs = S.fromList $ map (chr . read . ("0x" ++)) (words xs)
checkOneTestCase cps f = do
res <- f (tuplify cps)
when (not res) $ do
strs <- mapM toList cps
let codes = intercalate ";" $ map strToHex strs
txt = intercalate "; " strs
putStrLn ("Failed at line: " ++ show lineno)
putStrLn line
putStrLn $ codes ++ "; # (" ++ txt
error "Bailing out"
tuplify [c1, c2, c3, c4, c5] = (c1, c2, c3, c4, c5)
tuplify _ = error "tuplify bad arguments"
checkLine :: (Int, String) -> IO ()
checkLine (lineno, line) = do
-- marker lines indicating a test block start with @
if "@" `isPrefixOf` line
then
putStrLn line
else
checkAllTestCases lineno line
testNormalize :: FilePath -> IO ()
testNormalize file = do
contents <- readFile file
let ls = lines contents -- split into lines
& map (dropWhile isSpace) -- trim leading spaces
& zip [1..] -- add numbering
& filter (not . null . snd) -- remove blank lines
& filter (not . ("#" `isPrefixOf`) . snd) -- remove comments
checkAll ls
where
checkAll (x:xs) = checkLine x >> checkAll xs
checkAll [] = return ()
filesDir :: String
filesDir = "test/Streamly/Test/Unicode"
main :: IO ()
main = do
cdir <- Dir.getCurrentDirectory
testNormalize $ cdir </> filesDir </> "ucd/NormalizationTest.txt"
-- Additional test cases not in the unicode standard suite
testNormalize $ cdir </> filesDir </> "extra/NormalizationTest.txt"