packages feed

gibberish-3.0.0.0: src/Data/Gibberish/Gen/Trigraph.hs

{-# LANGUAGE OverloadedLists #-}

module Data.Gibberish.Gen.Trigraph
  ( genTrigraph,
    loadTrigraph,
  ) where

import Data.Gibberish.Errors (GibberishErr (..))
import Data.Gibberish.Types
import Data.Gibberish.Utils (toQwertyKey)
import Paths_gibberish (getDataFileName)

import Control.Exception (throwIO)
import Control.Monad (unless)
import Data.Aeson qualified as Aeson
import Data.Char (isPunctuation, toLower)
import Data.Map.Strict (Map ())
import Data.Map.Strict qualified as Map
import Data.Maybe (fromJust)
import Data.Text (Text ())
import Data.Text qualified as Text
import System.Directory (doesFileExist)
import System.FilePath ((</>))

-- | Generate trigraphs from a list of words
genTrigraph :: [Text] -> Trigraph
genTrigraph = Trigraph . foldr (foldWord . normalizeWord) Map.empty
  where
    foldWord = Map.unionWith combine . mkTrigraph
    combine (Frequencies f1) (Frequencies f2) = Frequencies $ Map.unionWith (+) f1 f2

-- | Generate a trigraph from a single word
mkTrigraph :: Text -> Map Digram Frequencies
mkTrigraph word = foldr insert' Map.empty $ scanTrigrams word
  where
    insert' (Trigram a b c) =
      Map.insertWith combineFrequencies (Digram a b) (mkFrequencies c)
    combineFrequencies (Frequencies m1) (Frequencies m2) =
      Frequencies (Map.unionWith (+) m1 m2)
    mkFrequencies c = Frequencies $ Map.singleton (Unigram c) 1

-- | Normalize a word before calculating the trigraph:
--
--  1. Remove punctuation (quotes, dashes, and so on)
--  2. Lower case all letters
--  3. Translate non-qwerty chars to qwerty keys (eg, à -> a)
normalizeWord :: Text -> Text
normalizeWord = Text.map transformChar . Text.filter filterChar
  where
    transformChar :: Char -> Char
    transformChar = toQwertyKey . toLower

    filterChar :: Char -> Bool
    filterChar = not . isPunctuation

scanTrigrams :: Text -> [Trigram]
scanTrigrams word = case Text.take 3 word of
  [a, b, c] -> Trigram a b c : scanTrigrams (Text.tail word)
  _ -> []

loadTrigraph :: Language -> IO Trigraph
loadTrigraph English = loadBuiltinTrigraph "wamerican.json"
loadTrigraph Spanish = loadBuiltinTrigraph "wspanish.json"
loadTrigraph (CustomTrigraph cfg) = loadTrigraphFromFile (unTrigraphConfig cfg)

loadBuiltinTrigraph :: FilePath -> IO Trigraph
loadBuiltinTrigraph file' = loadTrigraphFromFile =<< getBuiltinFilePath file'
  where
    getBuiltinFilePath basename = getDataFileName ("data" </> "trigraphs" </> basename)

loadTrigraphFromFile :: FilePath -> IO Trigraph
loadTrigraphFromFile file' = do
  exists <- doesFileExist file'
  unless exists $
    throwIO (TrigraphNotFound file')

  fromJust <$> Aeson.decodeFileStrict file'