{-# LANGUAGE OverloadedStrings #-} {-| Module : Text.Pronounce Description : A library for interfacing with the CMU Pronouncing Dictionary Copyright : (c) Noah Goodman, 2018 License : BSD3 Stability : experimental This is a library for interpresting the parsed Carnegie Mellon University Pronouncing Dictionary. It is modelled after Allison Parrish's python library, @pronouncing@. -} module Text.Pronounce ( -- * Datatypes CMUdict , DictComp , EntryWord , Phones , Stress -- * Using Text.Pronounce , initDict , stdDict , runPronounce -- * Basic Functions , phonesForEntry , stressesForEntry , stresses , syllableCount -- * Searching the Dictionary , searchDictBy , search , searchStresses -- * Rhyming , rhymingPart , rhymes -- * Some Helper Functions , dictAppend , (<||>) , liftD ) where import Text.Pronounce.ParseDict import Control.Monad.Reader import Data.Char (isDigit) import qualified Data.Text as T import qualified Data.Map as Map -- | We are using the Reader monad to perform computations in the context of the -- CMU dictionary without having to pass it in or worry about initializing every time type DictComp = Reader CMUdict -- | Convenient type aliases for the @Text@ string containing the stress patern of a word type Stress = T.Text -- | Look up the pronunciation (list of possible phones) of a word in the -- dictionary phonesForEntry :: EntryWord -> DictComp [Phones] phonesForEntry = fmap concat . asks . Map.lookup -- | Gives the stress pattern for a given word in the dictionary stressesForEntry :: EntryWord -> DictComp [Stress] stressesForEntry = liftD stresses . phonesForEntry -- | Isolates the stress pattern from a sequence of phones stresses :: Phones -> Stress stresses = T.filter isDigit -- | Gives the syllable count of a given pronunciation syllableCount :: Phones -> Int syllableCount = T.length . stresses -- | Finds the rhyming part of the given phones. rhymingPart :: Phones -> Phones rhymingPart = T.unwords . reverse . takeWhileInc (not . (`T.isInfixOf` "12") . T.singleton . T.last) . reverse . T.words where takeWhileInc _ [] = [] takeWhileInc p (x:xs) = x : if p x then takeWhileInc p xs else [] -- | Initializes a dictionary computation based on a selector function that -- operates on an individual phones. It returns a @DictComp@ containing a @CMUdict@ -- of all the entries that have at least one value satisfying the predicate. searchDictBy :: (Phones -> Bool) -> DictComp CMUdict searchDictBy = asks . Map.filter . any -- | Given a sequence of phones, find all words that contain that sequence of -- phones search :: Phones -> DictComp [EntryWord] search = fmap Map.keys . searchDictBy . T.isInfixOf -- | Given a stress pattern, find all words that satisfy that pattern searchStresses :: Stress -> DictComp [EntryWord] searchStresses = fmap Map.keys . searchDictBy . (==) . stresses -- | Given a word, finds all other words that rhyme with it rhymes :: EntryWord -> DictComp [EntryWord] rhymes word = (\entryPart -> fmap (filter (/= word) . Map.keys) . return . Map.filter (or . ((==) <$> entryPart <*>) . fmap rhymingPart) =<< ask ) =<< (liftD rhymingPart . phonesForEntry $ word) -- | Useful for nondeterministically combining several dictionary computations. -- Generally, one would call @foldr1 (\<||\>)@ to get all the possible results of -- mapping a @DictComp@ over a line of text (multiple words). dictAppend, (<||>) :: (Applicative f, Monoid a) => DictComp (f a) -> DictComp (f a) -> DictComp (f a) dictAppend = ((<*>) . fmap ((<*>) . fmap mappend)) infixl 3 <||> (<||>) = dictAppend -- | Lift functions to act on elements within a functor in a dictionary -- computation, such as a list of possible phones or stresses liftD :: (Functor f) => (a -> b) -> DictComp (f a) -> DictComp (f b) liftD = fmap . fmap -- | Get the value from a series of Dictionary Computations by supplying the -- dictionary to the computation. This is just @runReader@. runPronounce :: DictComp a -> CMUdict -> a runPronounce = runReader