{-# LANGUAGE DeriveGeneric, DerivingVia #-} {-# LANGUAGE RecordWildCards, QuasiQuotes #-} module Language.Coformat.Score ( Score , calcScore , PreparedFile , filename , prepareFile ) where import qualified Data.ByteString.Char8 as BS import qualified Data.IntMap.Strict as IM import Control.Monad.IO.Class import Data.Char import Data.Monoid import Data.String.Interpolate import GHC.Generics import Generic.Data type CharsHist = IM.IntMap Int data PreparedFile = PreparedFile { filename :: FilePath , contents :: BS.ByteString , charsHist :: CharsHist } deriving (Eq) prepareFile :: MonadIO m => FilePath -> m PreparedFile prepareFile filename = do contents <- liftIO $ BS.readFile filename let charsHist = calcCharsHist contents pure $ PreparedFile { .. } data Score = Score { significantLettersCountsDiff :: Sum Int , spacesMisalignment :: Sum Int } deriving (Eq, Ord, Bounded, Generic) deriving (Semigroup, Monoid) via (Generically Score) instance Show Score where show Score { .. } = [i|Score { #{getSum significantLettersCountsDiff} / #{getSum spacesMisalignment} }|] calcScore :: PreparedFile -> BS.ByteString -> Score calcScore prepared str = Score { .. } where significantLettersCountsDiff = Sum $ calcCharsDiff (charsHist prepared) (calcCharsHist str) spacesMisalignment | significantLettersCountsDiff /= 0 = 0 | otherwise = Sum $ alignSpaces (contents prepared) str calcCharsDiff :: CharsHist -> CharsHist -> Int calcCharsDiff hm1 hm2 = sum $ IM.elems $ IM.unionWith (\v1 v2 -> abs $ v1 - v2) hm1 hm2 calcCharsHist :: BS.ByteString -> CharsHist calcCharsHist = BS.foldl' ins mempty where ins hm ch | isSpace ch = hm | otherwise = IM.insertWith (+) (ord ch) 1 hm alignSpaces :: BS.ByteString -> BS.ByteString -> Int alignSpaces bs1 bs2 = go (BS.unpack bs1) (BS.unpack bs2) where go s1 [] = length s1 go [] s2 = length s2 go (c1:s1) (c2:s2) | c1 == c2 = go s1 s2 | isSpace c1 && isSpace c2 = 2 + go s1 s2 | isSpace c1 = 1 + go s1 (c2:s2) | isSpace c2 = 1 + go (c1:s1) s2 | otherwise = 2 + length s1 + length s2