packages feed

alfred-margaret-2.1.0.0: src/Data/Text/Utf8/Unlower.hs

module Data.Text.Utf8.Unlower
  (
    unlowerCodePoint

  , printUnlowerings
  ) where

import Control.Monad (forM_)

import qualified Data.Char as Char
import qualified Data.HashMap.Strict as HashMap
import qualified Data.List as List


-- | Inverse of Char.toLower/Utf8.lowerCodePoint
--
-- Returns all the characters that have the given character as their lower case, for example:
--
--    unlowerCodePoint 'a' == "aA"
--    unlowerCodePoint 'A' == ""
--    unlowerCodePoint '1' == "1"
--    unlowerCodePoint 'i' == "İiI"
--    unlowerCodePoint 'ß' == "ẞß"
--
unlowerCodePoint :: Char -> [Char]
unlowerCodePoint =
  \c -> maybe [c] id $ HashMap.lookup c unlowerings

-- | This map contains all the unlowerings for which the result is not just a singleton with the
-- input character. It's marked NOINLINE to make sure that it only gets constructed once.
unlowerings :: HashMap.HashMap Char [Char]
{-# NOINLINE unlowerings #-}
unlowerings =
  HashMap.filterWithKey isNotId $ List.foldl' (flip addUnlowering) initialMap [minBound..maxBound]
  where
    initialMap = HashMap.fromList $ zip [minBound..maxBound] (repeat [])
    addUnlowering c hm =
      HashMap.insertWith (++) (Char.toLower c) [c] hm
    isNotId lc ucs = ucs /= [lc]


-- | This function prints all the special cases of unlowerCodePoint where it's not @(pure . id)@:
--
--     SPECIAL: i (105) -> İ (304) i (105) I (73)
--     SPECIAL: k (107) -> K (8490) k (107) K (75)
--     SPECIAL: ß (223) -> ẞ (7838) ß (223)
--     SPECIAL: å (229) -> Å (8491) å (229) Å (197)
--     SPECIAL: dž (454) -> dž (454) Dž (453) DŽ (452)
--     SPECIAL: lj (457) -> lj (457) Lj (456) LJ (455)
--     SPECIAL: nj (460) -> nj (460) Nj (459) NJ (458)
--     SPECIAL: dz (499) -> dz (499) Dz (498) DZ (497)
--     SPECIAL: θ (952) -> ϴ (1012) θ (952) Θ (920)
--     SPECIAL: ω (969) -> Ω (8486) ω (969) Ω (937)
--     [..]
--     Inverse of Char.toUpper: a (97) -> a (97) A (65)
--     Inverse of Char.toUpper: b (98) -> b (98) B (66)
--     Inverse of Char.toUpper: c (99) -> c (99) C (67)
--     [..]
--
printUnlowerings :: IO ()
printUnlowerings = do

  let
    showCP :: Char -> String
    showCP c = case Char.ord c of
      co | co > 68000 -> show co -- Some RTL languages above these code points are annoying to print
      co -> c : " (" <> show co <> ")"

    showCPs :: [Char] -> String
    showCPs cs = List.intercalate " " (map showCP cs)

    isInverse (lc, ucs) = ucs == [lc, Char.toUpper lc] || ucs == [Char.toUpper lc, lc]
    isAlreadyUppercase (_, ucs) = ucs == []
    isSpecial p = not (isInverse p) && not (isAlreadyUppercase p)

    lst :: [(Char, [Char])]
    lst = HashMap.toList unlowerings

  forM_ (filter isSpecial lst) $ \(lc, ucs) -> do
    putStrLn $ "SPECIAL: " <> showCP lc <> " -> " <> showCPs ucs

  forM_ (filter isAlreadyUppercase lst) $ \(lc, _) -> do
    putStrLn $ "Already uppercase (there is no unlowering): " <> showCP lc

  forM_ (filter isInverse lst) $ \(lc, ucs) -> do
    putStrLn $ "Inverse of Char.toUpper: " <> showCP lc <> " -> " <> List.intercalate " " (map showCP ucs)