unicode-data-scripts-0.5.0: test/Unicode/Char/General/ScriptsSpec.hs
{-# LANGUAGE BlockArguments, OverloadedLists #-}
module Unicode.Char.General.ScriptsSpec
( spec
) where
import Data.Foldable (traverse_)
import qualified Data.List.NonEmpty as NE
import GHC.Exts
( isTrue#, orI#, andI#, (-#), (<#), (<=#), (==#)
, Char (..), ord#
, plusAddr#, eqAddr#, nullAddr#, ltAddr# )
import Test.Hspec
( expectationFailure, shouldBe, shouldSatisfy, it, describe
, Spec, Expectation, HasCallStack )
import Unicode.Internal.Bits.Scripts (nextInt32#)
import qualified Unicode.Char.General.Scripts as S
import qualified Unicode.Internal.Char.Scripts as IS
{- [NOTE]
These tests may fail if the compiler’s Unicode version
does not match the version of this package.
+-------------+----------------+-----------------+
| GHC version | @base@ version | Unicode version |
+=============+================+=================+
| 8.8 | 4.13 | 12.0 |
| 8.10.[1-4] | 4.14.{0,1} | 12.0 |
| 8.10.5+ | 4.14.2+ | 13.0 |
| 9.0.[1-2] | 4.15.0 | 12.1 |
| 9.2.[1-6] | 4.16.0 | 14.0 |
| 9.4.[1-4] | 4.17.0 | 14.0 |
| 9.6.1 | 4.18.0 | 15.0 |
+-------------+----------------+-----------------+
-}
spec :: Spec
spec = do
describe "Unicode scripts" do
describe "Examples" do
it "script" do
let check s = (== s) . S.script
minBound `shouldSatisfy` check S.Common
maxBound `shouldSatisfy` check S.Unknown
'.' `shouldSatisfy` check S.Common
'1' `shouldSatisfy` check S.Common
'A' `shouldSatisfy` check S.Latin
'Α' `shouldSatisfy` check S.Greek -- Greek capital
'α' `shouldSatisfy` check S.Greek
'\x0300' `shouldSatisfy` check S.Inherited
'\x0485' `shouldSatisfy` check S.Inherited
'\x0600' `shouldSatisfy` check S.Arabic
'\x060c' `shouldSatisfy` check S.Common
'\x0965' `shouldSatisfy` check S.Common
'\x1100' `shouldSatisfy` check S.Hangul
'\x3000' `shouldSatisfy` check S.Common
'\x4E00' `shouldSatisfy` check S.Han
'\x11FD0' `shouldSatisfy` check S.Tamil
'\x1F600' `shouldSatisfy` check S.Common
'\x20000' `shouldSatisfy` check S.Han
-- BOM
'\xFEFF' `shouldSatisfy` check S.Common
'\xFFFF' `shouldSatisfy` check S.Unknown
-- Private Use Areas
'\xE000' `shouldSatisfy` check S.Unknown
'\xF0000' `shouldSatisfy` check S.Unknown
it "scriptExtensions" do
let shouldBe' :: (HasCallStack) => Char -> NE.NonEmpty S.Script -> Expectation
shouldBe' c = shouldBe (S.scriptExtensions c)
minBound `shouldBe'` [ S.Common]
maxBound `shouldBe'` [ S.Unknown]
'.' `shouldBe'` [ S.Common]
'1' `shouldBe'` [ S.Common]
'A' `shouldBe'` [ S.Latin]
'Α' `shouldBe'` [ S.Greek]
'α' `shouldBe'` [ S.Greek]
'\x0300' `shouldBe'` [ S.Cherokee
, S.Coptic
, S.Cyrillic
, S.Greek
, S.Latin
, S.OldPermic
, S.Sunuwar
, S.TaiLe ]
'\x031F' `shouldBe'` [ S.Inherited]
'\x0485' `shouldBe'` [ S.Cyrillic, S.Latin]
'\x0600' `shouldBe'` [ S.Arabic]
'\x060C' `shouldBe'` [ S.Arabic
, S.Garay
, S.HanifiRohingya
, S.Nko
, S.Syriac
, S.Thaana
, S.Yezidi ]
'\x0965' `shouldBe'` [ S.Bengali
, S.Devanagari
, S.Dogra
, S.Grantha
, S.Gujarati
, S.GunjalaGondi
, S.Gurmukhi
, S.GurungKhema
, S.Kannada
, S.Khudawadi
, S.Limbu
, S.Mahajani
, S.Malayalam
, S.MasaramGondi
, S.Nandinagari
, S.OlOnal
, S.Oriya
, S.Sinhala
, S.SylotiNagri
, S.Takri
, S.Tamil
, S.Telugu
, S.Tirhuta ]
'\x1100' `shouldBe'` [ S.Hangul]
'\x1805' `shouldBe'` [ S.Mongolian, S.PhagsPa ]
'\x3001' `shouldBe'` [ S.Bopomofo
, S.Han
, S.Hangul
, S.Hiragana
, S.Katakana
, S.Mongolian
, S.Yi ]
'\x4E00' `shouldBe'` [ S.Han]
'\x11FD0' `shouldBe'` [ S.Grantha, S.Tamil ]
'\x1F600' `shouldBe'` [ S.Common]
'\x20000' `shouldBe'` [ S.Han]
-- BOM
'\xFEFF' `shouldBe'` [ S.Common ]
'\xFFFF' `shouldBe'` [ S.Unknown ]
-- Private Use Areas
'\xE000' `shouldBe'` [ S.Unknown ]
'\xF0000' `shouldBe'` [ S.Unknown ]
it "scriptDefinition" do
take 304 (S.scriptDefinition S.Latin) `shouldBe`
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzªºÀÁÂÃÄÅÆÇ\
\ÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹Ćć\
\ĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅ\
\ņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃ\
\ƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻ"
S.scriptDefinition S.ZanabazarSquare `shouldBe`
['\x11A00'..'\x11A47']
S.scriptDefinition S.Lydian `shouldBe`
(['\x10920'..'\x10939'] <> "\x1093F")
it "Smallest script definitions have at least one range in their bitmap"
let {
check s = case IS.scriptDefinition s of
(# _, _, addr#, n# #) -> case n# of
0# -> isTrue# (addr# `eqAddr#` nullAddr#)
4# ->
let lower = nextInt32# addr#
upper = nextInt32# (addr# `plusAddr#` 4#)
-- Check we have a range
in isTrue# ((lower <# IS.ScriptCharMask) `andI#`
(upper <# IS.ScriptCharMask) `andI#`
(1# <# (upper -# lower)) )
_ -> True
} in traverse_ (`shouldSatisfy` check) (enumFromTo minBound maxBound)
it "Characters are in the definition of their corresponding script"
let {
check c =
let s = S.script c
in if c `inScript` s
then pure ()
else expectationFailure $ mconcat
[ "Char “", show c, "” in not in the definition of “"
, show s, "”." ]
} in traverse_ check (enumFromTo minBound maxBound)
it "Characters in a script definition have the corresponding script"
let {
checkChar s c = let s' = S.script c in if s' == s
then pure ()
else expectationFailure $ mconcat
[ "Script is different for “", show c, "”. Expected: “"
, show s, "” but got: “", show s', "”." ];
check s = case S.scriptDefinition s of
[] -> expectationFailure $ mconcat
["Script “", show s, "” has an empty definition"]
chars -> traverse_ (checkChar s) chars
} in traverse_ check (enumFromTo minBound maxBound)
it "Characters in with a script extension different from its script"
let {
check c =
let script = S.script c
exts = S.scriptExtensions c
in if exts == pure script
|| (isSpecialScript script && script `notElem` exts)
|| (script `elem` exts)
then pure ()
else expectationFailure (show (c, script, exts));
isSpecialScript = \case
S.Common -> True
S.Inherited -> True
_ -> False
} in traverse_ check (enumFromTo minBound maxBound)
-- | Return 'True' if a character is in a script.
-- Faster than checking scriptDefinition directly.
inScript :: Char -> S.Script -> Bool
inScript (C# c#) s = case IS.scriptDefinition s of
(# lower#, upper#, addr0#, offset# #) ->
case (cp# <# lower#) `orI#` (upper# <# cp#) of
1# -> False
_ -> case offset# of
0# -> True
_ -> isTrue# ((cp# ==# lower#) `orI#` (cp# ==# upper#))
|| check addr0#
where
cp# = ord# c#
addr1# = addr0# `plusAddr#` offset#
check addr# = case addr1# `ltAddr#` addr# of
1# -> False
_ -> case nextInt32# addr# of
cp1# -> case andI# cp1# IS.ScriptCharMask of
-- Range
0# ->
isTrue# ((cp1# <=# cp#) `andI#`
(cp# <=# nextInt32# (addr# `plusAddr#` 4#)))
|| check (addr# `plusAddr#` 8#)
-- Single char
_ -> case andI# IS.ScriptCharMaskComplement cp1# -# cp# of
0# -> True
_ -> check (addr# `plusAddr#` 4#)