packages feed

brassica-1.1.0: src/Brassica/Paradigm/Apply.hs

{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE LambdaCase #-}

module Brassica.Paradigm.Apply
       ( ResultsTree(..)
       , applyParadigm
       , formatNested
       , depth
       ) where

import Brassica.Paradigm.Types

import Data.Functor ((<&>))
import Data.List (sortOn, foldl', intercalate, nub)
import Data.Maybe (mapMaybe)
import Data.Ord (Down(Down))

-- | Results from applying a 'Paradigm' to a word, represented as a tree
-- containing each possible choice of 'Grammeme's.
data ResultsTree a = Node [ResultsTree a] | Result a
    deriving (Show, Functor, Foldable)

addLevel :: (a -> [b]) -> ResultsTree a -> ResultsTree b
addLevel f (Result r) =
    case f r of
        [r'] -> Result r'
        r' -> Node $ Result <$> r'
addLevel f (Node rs) = Node $ addLevel f <$> rs

-- | Depth of a 'ResultsTree': i.e. how many levels of 'Node's it
-- contains.
depth :: ResultsTree a -> Int
depth (Node ts) = maximum $ (1+) . depth <$> ts
depth (Result _) = 0

-- | Formats a 'ResultsTree' in a nested way, where the lowest-level
-- elements are separated by one space, the second-lowest are
-- separated by one newline, the third-lowest by two newlines, and so
-- on.
formatNested :: (a -> String) -> ResultsTree a -> String
formatNested f = snd . go
  where
    go (Result a) = (0, f a)
    go (Node rts) =
        let (depths, formatted) = unzip $ go <$> rts
            depth' = maximum depths
            separator =
                if depth' == 0
                then " "
                else replicate depth' '\n'
        in (1+depth', intercalate separator formatted)

-- | Apply the given 'Paradigm' to a root, to produce all possible
-- derived forms.
applyParadigm :: Paradigm -> Maybe String -> [String] -> ResultsTree String
applyParadigm p pclass ws =
    let fs = mapMaybe getFeature p
        ms = mapMaybe getMapping p
    in addLevel (applyTo ws . expand ms) $ combinations pclass fs
  where
    getFeature (NewFeature f) = Just f
    getFeature _ = Nothing

    getMapping (NewMapping k v) = Just (k,v)
    getMapping _ = Nothing

evalExpr :: (a -> Bool) -> Expr a -> Bool
evalExpr p (Any xs) = any (evalExpr p) xs
evalExpr p (All xs) = all (evalExpr p) xs
evalExpr p (Pred a) = p a

combinations :: Maybe String -> [Feature] -> ResultsTree [Grammeme]
combinations pclass =
    let initGrammemes = case pclass of
            Just p -> [(Just Class, Abstract (AbstractGrammeme p))]
            _ -> []
    in (fmap.fmap) snd . foldl' go (Result initGrammemes)
  where
    addFeature
        :: Feature
        -> [(Maybe FeatureName, Grammeme)]
        -> [[(Maybe FeatureName, Grammeme)]]
    addFeature (Feature c n gs) acc
        | evalExpr (satisfied (flip lookup acc . Just)) c
        = gs <&> \g -> (FeatureName <$> n, g):acc
        | otherwise
        = [acc]

    go  :: ResultsTree [(Maybe FeatureName, Grammeme)]
        -> Feature
        -> ResultsTree [(Maybe FeatureName, Grammeme)]
    go rt f = addLevel (addFeature f) rt

    satisfied
        :: (FeatureName -> Maybe Grammeme)
        -> Condition
        -> Bool
    satisfied l (Is n g) = case l n of
        Just g' -> g == g'
        Nothing -> False
    satisfied l (Not n g) = case l n of
        Just g' -> g /= g'
        Nothing -> True

expand :: [(Expr AbstractGrammeme, Affix)] -> [Grammeme] -> [Process]
expand ms = concat . ((++) <$> concretes <*> (replace . filterAbstract))
  where
    concretes :: [Grammeme] -> [Affix]
    concretes = mapMaybe $ \case
        Concrete affix -> Just affix
        Abstract _ -> Nothing

    filterAbstract :: [Grammeme] -> [AbstractGrammeme]
    filterAbstract = mapMaybe $ \case
        Concrete _ -> Nothing
        Abstract g -> Just g

    replace :: [AbstractGrammeme] -> [Affix]
    replace gs = do
        (condition, replacement) <- ms
        if evalExpr (`elem` gs) condition
           then return replacement
           else []

applyTo :: [String] -> [Process] -> [String]
applyTo ws is =
    let ps = concatMap snd $ sortOn (Down . fst) $ mapMaybe getPrefix is
        ss = concatMap snd $ sortOn         fst  $ mapMaybe getSuffix is
        stems = case nub (mapMaybe getStem is) of
            [] | [_] <- ws -> ws
            [s] | s >= 1 && s <= length ws -> [ws !! (s-1)]
            s -> [show s]
    in stems <&> \stem -> ps ++ stem ++ ss
  where
    getPrefix (Prefix s i) = Just (s,i)
    getPrefix _ = Nothing

    getSuffix (Suffix s i) = Just (s,i)
    getSuffix _ = Nothing

    getStem (Stem i) = Just i
    getStem _ = Nothing